Add ParseContext class

This holds the parsers and information about the current parse run.

Change-Id: I107e01a404e35a863e3e069fd0158448e546d5d5
diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
index a3de474..dcef666 100644
--- a/tests/unit/test_model.py
+++ b/tests/unit/test_model.py
@@ -47,6 +47,8 @@
         self.pipeline = model.Pipeline('gate', self.layout)
         self.layout.addPipeline(self.pipeline)
         self.queue = model.ChangeQueue(self.pipeline)
+        self.pcontext = configloader.ParseContext(
+            None, None, self.tenant, self.layout)
 
         private_key_file = os.path.join(FIXTURE_DIR, 'private.pem')
         with open(private_key_file, "rb") as f:
@@ -61,10 +63,7 @@
 
     @property
     def job(self):
-        tenant = model.Tenant('tenant')
-        layout = model.Layout(tenant)
-        job_parser = configloader.JobParser(tenant, layout)
-        job = job_parser.fromYaml({
+        job = self.pcontext.job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'job',
@@ -148,34 +147,27 @@
             job.applyVariant(bad_final)
 
     def test_job_inheritance_job_tree(self):
-        tenant = model.Tenant('tenant')
-        layout = model.Layout(tenant)
-
-        tpc = model.TenantProjectConfig(self.project)
-        tenant.addUntrustedProject(tpc)
-
-        pipeline = model.Pipeline('gate', layout)
-        layout.addPipeline(pipeline)
+        pipeline = model.Pipeline('gate', self.layout)
+        self.layout.addPipeline(pipeline)
         queue = model.ChangeQueue(pipeline)
 
-        job_parser = configloader.JobParser(tenant, layout)
-        base = job_parser.fromYaml({
+        base = self.pcontext.job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'base',
             'parent': None,
             'timeout': 30,
         })
-        layout.addJob(base)
-        python27 = job_parser.fromYaml({
+        self.layout.addJob(base)
+        python27 = self.pcontext.job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'python27',
             'parent': 'base',
             'timeout': 40,
         })
-        layout.addJob(python27)
-        python27diablo = job_parser.fromYaml({
+        self.layout.addJob(python27)
+        python27diablo = self.pcontext.job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'python27',
@@ -184,13 +176,9 @@
             ],
             'timeout': 50,
         })
-        layout.addJob(python27diablo)
+        self.layout.addJob(python27diablo)
 
-        project_template_parser = configloader.ProjectTemplateParser(
-            tenant, layout)
-        project_parser = configloader.ProjectParser(
-            tenant, layout, project_template_parser)
-        project_config = project_parser.fromYaml([{
+        project_config = self.pcontext.project_parser.fromYaml([{
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'project',
@@ -201,12 +189,12 @@
                 ]
             }
         }])
-        layout.addProjectConfig(project_config)
+        self.layout.addProjectConfig(project_config)
 
         change = model.Change(self.project)
         change.branch = 'master'
         item = queue.enqueueChange(change)
-        item.layout = layout
+        item.layout = self.layout
 
         self.assertTrue(base.changeMatches(change))
         self.assertTrue(python27.changeMatches(change))
@@ -220,7 +208,7 @@
 
         change.branch = 'stable/diablo'
         item = queue.enqueueChange(change)
-        item.layout = layout
+        item.layout = self.layout
 
         self.assertTrue(base.changeMatches(change))
         self.assertTrue(python27.changeMatches(change))
@@ -233,26 +221,19 @@
         self.assertEqual(job.timeout, 70)
 
     def test_inheritance_keeps_matchers(self):
-        tenant = model.Tenant('tenant')
-        layout = model.Layout(tenant)
-
-        pipeline = model.Pipeline('gate', layout)
-        layout.addPipeline(pipeline)
+        pipeline = model.Pipeline('gate', self.layout)
+        self.layout.addPipeline(pipeline)
         queue = model.ChangeQueue(pipeline)
-        project = model.Project('project', self.source)
-        tpc = model.TenantProjectConfig(project)
-        tenant.addUntrustedProject(tpc)
 
-        job_parser = configloader.JobParser(tenant, layout)
-        base = job_parser.fromYaml({
+        base = self.pcontext.job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'base',
             'parent': None,
             'timeout': 30,
         })
-        layout.addJob(base)
-        python27 = job_parser.fromYaml({
+        self.layout.addJob(base)
+        python27 = self.pcontext.job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'python27',
@@ -260,13 +241,9 @@
             'timeout': 40,
             'irrelevant-files': ['^ignored-file$'],
         })
-        layout.addJob(python27)
+        self.layout.addJob(python27)
 
-        project_template_parser = configloader.ProjectTemplateParser(
-            tenant, layout)
-        project_parser = configloader.ProjectParser(
-            tenant, layout, project_template_parser)
-        project_config = project_parser.fromYaml([{
+        project_config = self.pcontext.project_parser.fromYaml([{
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'project',
@@ -276,13 +253,13 @@
                 ]
             }
         }])
-        layout.addProjectConfig(project_config)
+        self.layout.addProjectConfig(project_config)
 
-        change = model.Change(project)
+        change = model.Change(self.project)
         change.branch = 'master'
         change.files = ['/COMMIT_MSG', 'ignored-file']
         item = queue.enqueueChange(change)
-        item.layout = layout
+        item.layout = self.layout
 
         self.assertTrue(base.changeMatches(change))
         self.assertFalse(python27.changeMatches(change))
@@ -291,29 +268,26 @@
         self.assertEqual([], item.getJobs())
 
     def test_job_source_project(self):
-        tenant = self.tenant
-        layout = self.layout
         base_project = model.Project('base_project', self.source)
         base_context = model.SourceContext(base_project, 'master',
                                            'test', True)
         tpc = model.TenantProjectConfig(base_project)
-        tenant.addUntrustedProject(tpc)
+        self.tenant.addUntrustedProject(tpc)
 
-        job_parser = configloader.JobParser(tenant, layout)
-        base = job_parser.fromYaml({
+        base = self.pcontext.job_parser.fromYaml({
             '_source_context': base_context,
             '_start_mark': self.start_mark,
             'parent': None,
             'name': 'base',
         })
-        layout.addJob(base)
+        self.layout.addJob(base)
 
         other_project = model.Project('other_project', self.source)
         other_context = model.SourceContext(other_project, 'master',
                                             'test', True)
         tpc = model.TenantProjectConfig(other_project)
-        tenant.addUntrustedProject(tpc)
-        base2 = job_parser.fromYaml({
+        self.tenant.addUntrustedProject(tpc)
+        base2 = self.pcontext.job_parser.fromYaml({
             '_source_context': other_context,
             '_start_mark': self.start_mark,
             'name': 'base',
@@ -322,12 +296,11 @@
                 Exception,
                 "Job base in other_project is not permitted "
                 "to shadow job base in base_project"):
-            layout.addJob(base2)
+            self.layout.addJob(base2)
 
     def test_job_pipeline_allow_untrusted_secrets(self):
         self.pipeline.post_review = False
-        job_parser = configloader.JobParser(self.tenant, self.layout)
-        job = job_parser.fromYaml({
+        job = self.pcontext.job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'job',
@@ -337,11 +310,7 @@
 
         self.layout.addJob(job)
 
-        project_template_parser = configloader.ProjectTemplateParser(
-            self.tenant, self.layout)
-        project_parser = configloader.ProjectParser(
-            self.tenant, self.layout, project_template_parser)
-        project_config = project_parser.fromYaml(
+        project_config = self.pcontext.project_parser.fromYaml(
             [{
                 '_source_context': self.context,
                 '_start_mark': self.start_mark,
diff --git a/zuul/configloader.py b/zuul/configloader.py
index 2580cb2..d0c8562 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -365,8 +365,9 @@
 
     schema = vs.Schema(pragma)
 
-    def __init__(self):
+    def __init__(self, pcontext):
         self.log = logging.getLogger("zuul.PragmaParser")
+        self.pcontext = pcontext
 
     def fromYaml(self, conf):
         with configuration_exceptions('project-template', conf):
@@ -384,10 +385,9 @@
 
 
 class NodeSetParser(object):
-    def __init__(self, tenant, layout):
+    def __init__(self, pcontext):
         self.log = logging.getLogger("zuul.NodeSetParser")
-        self.tenant = tenant
-        self.layout = layout
+        self.pcontext = pcontext
 
     def getSchema(self, anonymous=False):
         node = {vs.Required('name'): to_list(str),
@@ -435,10 +435,9 @@
 
 
 class SecretParser(object):
-    def __init__(self, tenant, layout):
+    def __init__(self, pcontext):
         self.log = logging.getLogger("zuul.SecretParser")
-        self.tenant = tenant
-        self.layout = layout
+        self.pcontext = pcontext
         self.schema = self.getSchema()
 
     def getSchema(self):
@@ -542,10 +541,9 @@
         'override-checkout',
     ]
 
-    def __init__(self, tenant, layout):
+    def __init__(self, pcontext):
         self.log = logging.getLogger("zuul.JobParser")
-        self.tenant = tenant
-        self.layout = layout
+        self.pcontext = pcontext
 
     def _getImpliedBranches(self, job):
         # If the user has set a pragma directive for this, use the
@@ -564,7 +562,8 @@
 
         # If this project only has one branch, don't create implied
         # branch matchers.  This way central job repos can work.
-        branches = self.tenant.getProjectBranches(job.source_context.project)
+        branches = self.pcontext.tenant.getProjectBranches(
+            job.source_context.project)
         if len(branches) == 1:
             return None
 
@@ -610,10 +609,11 @@
         for secret_config in as_list(conf.get('secrets', [])):
             if isinstance(secret_config, str):
                 secret_name = secret_config
-                secret = self.layout.secrets.get(secret_name)
+                secret = self.pcontext.layout.secrets.get(secret_name)
             else:
                 secret_name = secret_config['name']
-                secret = self.layout.secrets.get(secret_config['secret'])
+                secret = self.pcontext.layout.secrets.get(
+                    secret_config['secret'])
             if secret is None:
                 raise SecretNotFoundError(secret_name)
             if secret_name == 'zuul' or secret_name == 'nodepool':
@@ -638,13 +638,15 @@
         if secrets and not conf['_source_context'].trusted:
             job.post_review = True
 
-        if conf.get('timeout') and self.tenant.max_job_timeout != -1 and \
-           int(conf['timeout']) > self.tenant.max_job_timeout:
-            raise MaxTimeoutError(job, self.tenant)
+        if (conf.get('timeout') and
+            self.pcontext.tenant.max_job_timeout != -1 and
+            int(conf['timeout']) > self.pcontext.tenant.max_job_timeout):
+            raise MaxTimeoutError(job, self.pcontext.tenant)
 
-        if conf.get('post-timeout') and self.tenant.max_job_timeout != -1 and \
-           int(conf['post-timeout']) > self.tenant.max_job_timeout:
-            raise MaxTimeoutError(job, self.tenant)
+        if (conf.get('post-timeout') and
+            self.pcontext.tenant.max_job_timeout != -1 and
+            int(conf['post-timeout']) > self.pcontext.tenant.max_job_timeout):
+            raise MaxTimeoutError(job, self.pcontext.tenant)
 
         if 'post-review' in conf:
             if conf['post-review']:
@@ -692,18 +694,18 @@
             if k in conf:
                 setattr(job, a, conf[k])
         if 'nodeset' in conf:
-            nodeset_parser = NodeSetParser(self.tenant, self.layout)
             conf_nodeset = conf['nodeset']
             if isinstance(conf_nodeset, str):
                 # This references an existing named nodeset in the layout.
-                ns = self.layout.nodesets.get(conf_nodeset)
+                ns = self.pcontext.layout.nodesets.get(conf_nodeset)
                 if ns is None:
                     raise NodesetNotFoundError(conf_nodeset)
             else:
-                ns = nodeset_parser.fromYaml(conf_nodeset, anonymous=True)
-            if self.tenant.max_nodes_per_job != -1 and \
-               len(ns) > self.tenant.max_nodes_per_job:
-                raise MaxNodeError(job, self.tenant)
+                ns = self.pcontext.nodeset_parser.fromYaml(
+                    conf_nodeset, anonymous=True)
+            if self.pcontext.tenant.max_nodes_per_job != -1 and \
+               len(ns) > self.pcontext.tenant.max_nodes_per_job:
+                raise MaxNodeError(job, self.pcontext.tenant)
             job.nodeset = ns
 
         if 'required-projects' in conf:
@@ -719,7 +721,8 @@
                     project_name = project
                     project_override_branch = None
                     project_override_checkout = None
-                (trusted, project) = self.tenant.getProject(project_name)
+                (trusted, project) = self.pcontext.tenant.getProject(
+                    project_name)
                 if project is None:
                     raise Exception("Unknown project %s" % (project_name,))
                 job_project = model.JobProject(project.canonical_name,
@@ -759,7 +762,7 @@
         if allowed_projects:
             allowed = []
             for p in as_list(allowed_projects):
-                (trusted, project) = self.tenant.getProject(p)
+                (trusted, project) = self.pcontext.tenant.getProject(p)
                 if project is None:
                     raise Exception("Unknown project %s" % (p,))
                 allowed.append(project.name)
@@ -788,7 +791,7 @@
     def _makeZuulRole(self, job, role):
         name = role['zuul'].split('/')[-1]
 
-        (trusted, project) = self.tenant.getProject(role['zuul'])
+        (trusted, project) = self.pcontext.tenant.getProject(role['zuul'])
         if project is None:
             return None
 
@@ -807,11 +810,9 @@
 
 
 class ProjectTemplateParser(object):
-    def __init__(self, tenant, layout):
+    def __init__(self, pcontext):
         self.log = logging.getLogger("zuul.ProjectTemplateParser")
-        self.tenant = tenant
-        self.layout = layout
-        self.schema = self.getSchema()
+        self.pcontext = pcontext
 
     def getSchema(self):
         project_template = {
@@ -832,18 +833,18 @@
             'jobs': job_list,
         }
 
-        for p in self.layout.pipelines.values():
+        for p in self.pcontext.layout.pipelines.values():
             project_template[p.name] = pipeline_contents
         return vs.Schema(project_template)
 
     def fromYaml(self, conf, validate=True):
         if validate:
             with configuration_exceptions('project-template', conf):
-                self.schema(conf)
+                self.getSchema()(conf)
         source_context = conf['_source_context']
         project_template = model.ProjectConfig(conf['name'], source_context)
         start_mark = conf['_start_mark']
-        for pipeline in self.layout.pipelines.values():
+        for pipeline in self.pcontext.layout.pipelines.values():
             conf_pipeline = conf.get(pipeline.name)
             if not conf_pipeline:
                 continue
@@ -872,20 +873,17 @@
             # validate that the job is existing
             with configuration_exceptions('project or project-template',
                                           attrs):
-                self.layout.getJob(jobname)
+                self.pcontext.layout.getJob(jobname)
 
-            job_parser = JobParser(self.tenant, self.layout)
-            job_list.addJob(job_parser.fromYaml(attrs, project_pipeline=True,
-                                                name=jobname, validate=False))
+            job_list.addJob(self.pcontext.job_parser.fromYaml(
+                attrs, project_pipeline=True,
+                name=jobname, validate=False))
 
 
 class ProjectParser(object):
-    def __init__(self, tenant, layout, project_template_parser):
+    def __init__(self, pcontext):
         self.log = logging.getLogger("zuul.ProjectParser")
-        self.tenant = tenant
-        self.layout = layout
-        self.project_template_parser = project_template_parser
-        self.schema = self.getSchema()
+        self.pcontext = pcontext
 
     def getSchema(self):
         project = {
@@ -907,18 +905,18 @@
             'jobs': job_list
         }
 
-        for p in self.layout.pipelines.values():
+        for p in self.pcontext.layout.pipelines.values():
             project[p.name] = pipeline_contents
         return vs.Schema(project)
 
     def fromYaml(self, conf_list):
         for conf in conf_list:
             with configuration_exceptions('project', conf):
-                self.schema(conf)
+                self.getSchema()(conf)
 
         with configuration_exceptions('project', conf_list[0]):
             project_name = conf_list[0]['name']
-            (trusted, project) = self.tenant.getProject(project_name)
+            (trusted, project) = self.pcontext.tenant.getProject(project_name)
             if project is None:
                 raise ProjectNotFoundError(project_name)
             project_config = model.ProjectConfig(project.canonical_name)
@@ -936,16 +934,16 @@
                 # parsing the definition as a template, then applying
                 # all of the templates, including the newly parsed
                 # one, in order.
-                project_template = self.project_template_parser.fromYaml(
-                    conf, validate=False)
+                project_template = self.pcontext.project_template_parser.\
+                    fromYaml(conf, validate=False)
                 # If this project definition is in a place where it
                 # should get implied branch matchers, set it.
                 if (not conf['_source_context'].trusted):
                     implied_branch = conf['_source_context'].branch
                 for name in conf_templates:
-                    if name not in self.layout.project_templates:
+                    if name not in self.pcontext.layout.project_templates:
                         raise TemplateNotFoundError(name)
-                configs.extend([(self.layout.project_templates[name],
+                configs.extend([(self.pcontext.layout.project_templates[name],
                                  implied_branch)
                                 for name in conf_templates])
                 configs.append((project_template, implied_branch))
@@ -963,7 +961,7 @@
             project_config.merge_mode = model.MERGER_MAP['merge-resolve']
         if project_config.default_branch is None:
             project_config.default_branch = 'master'
-        for pipeline in self.layout.pipelines.values():
+        for pipeline in self.pcontext.layout.pipelines.values():
             project_pipeline = model.ProjectPipelineConfig()
             queue_name = None
             debug = False
@@ -1000,12 +998,9 @@
         'disabled': 'disabled_actions',
     }
 
-    def __init__(self, tenant, layout, connections, scheduler):
+    def __init__(self, pcontext):
         self.log = logging.getLogger("zuul.PipelineParser")
-        self.tenant = tenant
-        self.layout = layout
-        self.connections = connections
-        self.scheduler = scheduler
+        self.pcontext = pcontext
 
     def getDriverSchema(self, dtype):
         methods = {
@@ -1018,7 +1013,7 @@
         schema = {}
         # Add the configured connections as available layout options
         for connection_name, connection in \
-            self.connections.connections.items():
+            self.pcontext.connections.connections.items():
             method = getattr(connection.driver, methods[dtype], None)
             if method:
                 schema[connection_name] = to_list(method())
@@ -1069,7 +1064,7 @@
     def fromYaml(self, conf):
         with configuration_exceptions('pipeline', conf):
             self.getSchema()(conf)
-        pipeline = model.Pipeline(conf['name'], self.layout)
+        pipeline = model.Pipeline(conf['name'], self.pcontext.layout)
         pipeline.description = conf.get('description')
 
         precedence = model.PRECEDENCE_MAP[conf.get('precedence')]
@@ -1099,8 +1094,8 @@
             if conf.get(conf_key):
                 for reporter_name, params \
                     in conf.get(conf_key).items():
-                    reporter = self.connections.getReporter(reporter_name,
-                                                            params)
+                    reporter = self.pcontext.connections.getReporter(
+                        reporter_name, params)
                     reporter.setAction(conf_key)
                     reporter_set.append(reporter)
             setattr(pipeline, action, reporter_set)
@@ -1126,26 +1121,27 @@
         manager_name = conf['manager']
         if manager_name == 'dependent':
             manager = zuul.manager.dependent.DependentPipelineManager(
-                self.scheduler, pipeline)
+                self.pcontext.scheduler, pipeline)
         elif manager_name == 'independent':
             manager = zuul.manager.independent.IndependentPipelineManager(
-                self.scheduler, pipeline)
+                self.pcontext.scheduler, pipeline)
 
         pipeline.setManager(manager)
-        self.layout.pipelines[conf['name']] = pipeline
+        self.pcontext.layout.pipelines[conf['name']] = pipeline
 
         for source_name, require_config in conf.get('require', {}).items():
-            source = self.connections.getSource(source_name)
+            source = self.pcontext.connections.getSource(source_name)
             manager.ref_filters.extend(
                 source.getRequireFilters(require_config))
 
         for source_name, reject_config in conf.get('reject', {}).items():
-            source = self.connections.getSource(source_name)
+            source = self.pcontext.connections.getSource(source_name)
             manager.ref_filters.extend(
                 source.getRejectFilters(reject_config))
 
         for trigger_name, trigger_config in conf.get('trigger').items():
-            trigger = self.connections.getTrigger(trigger_name, trigger_config)
+            trigger = self.pcontext.connections.getTrigger(
+                trigger_name, trigger_config)
             pipeline.triggers.append(trigger)
             manager.event_filters.extend(
                 trigger.getEventFilters(conf['trigger'][trigger_name]))
@@ -1154,10 +1150,9 @@
 
 
 class SemaphoreParser(object):
-    def __init__(self, tenant, layout):
+    def __init__(self, pcontext):
         self.log = logging.getLogger("zuul.SemaphoreParser")
-        self.tenant = tenant
-        self.layout = layout
+        self.pcontext = pcontext
         self.schema = self.getSchema()
 
     def getSchema(self):
@@ -1176,6 +1171,24 @@
         return semaphore
 
 
+class ParseContext(object):
+    """Hold information about a particular run of the parser"""
+
+    def __init__(self, connections, scheduler, tenant, layout):
+        self.connections = connections
+        self.scheduler = scheduler
+        self.tenant = tenant
+        self.layout = layout
+        self.pragma_parser = PragmaParser(self)
+        self.pipeline_parser = PipelineParser(self)
+        self.nodeset_parser = NodeSetParser(self)
+        self.secret_parser = SecretParser(self)
+        self.job_parser = JobParser(self)
+        self.semaphore_parser = SemaphoreParser(self)
+        self.project_template_parser = ProjectTemplateParser(self)
+        self.project_parser = ProjectParser(self)
+
+
 class TenantParser(object):
     def __init__(self, connections, scheduler, merger):
         self.log = logging.getLogger("zuul.TenantParser")
@@ -1592,45 +1605,43 @@
 
     def _parseLayoutItems(self, layout, tenant, data,
                           skip_pipelines=False, skip_semaphores=False):
+        pcontext = ParseContext(self.connections, self.scheduler,
+                                tenant, layout)
         # Handle pragma items first since they modify the source context
         # used by other classes.
-        pragma_parser = PragmaParser()
         for config_pragma in data.pragmas:
-            pragma_parser.fromYaml(config_pragma)
+            pcontext.pragma_parser.fromYaml(config_pragma)
 
-        pipeline_parser = PipelineParser(tenant, layout, self.connections,
-                                         self.scheduler)
         if not skip_pipelines:
             for config_pipeline in data.pipelines:
                 classes = self._getLoadClasses(tenant, config_pipeline)
                 if 'pipeline' not in classes:
                     continue
-                layout.addPipeline(pipeline_parser.fromYaml(config_pipeline))
+                layout.addPipeline(pcontext.pipeline_parser.fromYaml(
+                    config_pipeline))
 
-        nodeset_parser = NodeSetParser(tenant, layout)
         for config_nodeset in data.nodesets:
             classes = self._getLoadClasses(tenant, config_nodeset)
             if 'nodeset' not in classes:
                 continue
             with configuration_exceptions('nodeset', config_nodeset):
-                layout.addNodeSet(nodeset_parser.fromYaml(
+                layout.addNodeSet(pcontext.nodeset_parser.fromYaml(
                     config_nodeset))
 
-        secret_parser = SecretParser(tenant, layout)
         for config_secret in data.secrets:
             classes = self._getLoadClasses(tenant, config_secret)
             if 'secret' not in classes:
                 continue
             with configuration_exceptions('secret', config_secret):
-                layout.addSecret(secret_parser.fromYaml(config_secret))
+                layout.addSecret(pcontext.secret_parser.fromYaml(
+                    config_secret))
 
-        job_parser = JobParser(tenant, layout)
         for config_job in data.jobs:
             classes = self._getLoadClasses(tenant, config_job)
             if 'job' not in classes:
                 continue
             with configuration_exceptions('job', config_job):
-                job = job_parser.fromYaml(config_job)
+                job = pcontext.job_parser.fromYaml(config_job)
                 added = layout.addJob(job)
                 if not added:
                     self.log.debug(
@@ -1655,27 +1666,26 @@
             semaphore_layout = model.Layout(tenant)
         else:
             semaphore_layout = layout
-        semaphore_parser = SemaphoreParser(tenant, layout)
         for config_semaphore in data.semaphores:
             classes = self._getLoadClasses(
                 tenant, config_semaphore)
             if 'semaphore' not in classes:
                 continue
             with configuration_exceptions('semaphore', config_semaphore):
-                semaphore = semaphore_parser.fromYaml(config_semaphore)
+                semaphore = pcontext.semaphore_parser.fromYaml(
+                    config_semaphore)
                 semaphore_layout.addSemaphore(semaphore)
 
-        project_template_parser = ProjectTemplateParser(tenant, layout)
         for config_template in data.project_templates:
             classes = self._getLoadClasses(tenant, config_template)
             if 'project-template' not in classes:
                 continue
             with configuration_exceptions('project-template', config_template):
-                layout.addProjectTemplate(project_template_parser.fromYaml(
-                    config_template))
+                layout.addProjectTemplate(
+                    pcontext.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 flattened_projects.values():
             # Unlike other config classes, we expect multiple project
             # stanzas with the same name, so that a config repo can
@@ -1693,7 +1703,7 @@
             if not filtered_projects:
                 continue
 
-            layout.addProjectConfig(project_parser.fromYaml(
+            layout.addProjectConfig(pcontext.project_parser.fromYaml(
                 filtered_projects))
 
     def _flattenProjects(self, projects, tenant):