Make JobParser a regular class

Part of a series refactoring the configloader to make it more
maintainable.

Change-Id: Ie934dfa1bf7d05acee6b290284a23de65b7993c1
diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py
index 5c586ca..a3de474 100644
--- a/tests/unit/test_model.py
+++ b/tests/unit/test_model.py
@@ -63,7 +63,8 @@
     def job(self):
         tenant = model.Tenant('tenant')
         layout = model.Layout(tenant)
-        job = configloader.JobParser.fromYaml(tenant, layout, {
+        job_parser = configloader.JobParser(tenant, layout)
+        job = job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'job',
@@ -157,7 +158,8 @@
         layout.addPipeline(pipeline)
         queue = model.ChangeQueue(pipeline)
 
-        base = configloader.JobParser.fromYaml(tenant, layout, {
+        job_parser = configloader.JobParser(tenant, layout)
+        base = job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'base',
@@ -165,7 +167,7 @@
             'timeout': 30,
         })
         layout.addJob(base)
-        python27 = configloader.JobParser.fromYaml(tenant, layout, {
+        python27 = job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'python27',
@@ -173,7 +175,7 @@
             'timeout': 40,
         })
         layout.addJob(python27)
-        python27diablo = configloader.JobParser.fromYaml(tenant, layout, {
+        python27diablo = job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'python27',
@@ -241,7 +243,8 @@
         tpc = model.TenantProjectConfig(project)
         tenant.addUntrustedProject(tpc)
 
-        base = configloader.JobParser.fromYaml(tenant, layout, {
+        job_parser = configloader.JobParser(tenant, layout)
+        base = job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'base',
@@ -249,7 +252,7 @@
             'timeout': 30,
         })
         layout.addJob(base)
-        python27 = configloader.JobParser.fromYaml(tenant, layout, {
+        python27 = job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'python27',
@@ -296,7 +299,8 @@
         tpc = model.TenantProjectConfig(base_project)
         tenant.addUntrustedProject(tpc)
 
-        base = configloader.JobParser.fromYaml(tenant, layout, {
+        job_parser = configloader.JobParser(tenant, layout)
+        base = job_parser.fromYaml({
             '_source_context': base_context,
             '_start_mark': self.start_mark,
             'parent': None,
@@ -309,7 +313,7 @@
                                             'test', True)
         tpc = model.TenantProjectConfig(other_project)
         tenant.addUntrustedProject(tpc)
-        base2 = configloader.JobParser.fromYaml(tenant, layout, {
+        base2 = job_parser.fromYaml({
             '_source_context': other_context,
             '_start_mark': self.start_mark,
             'name': 'base',
@@ -322,7 +326,8 @@
 
     def test_job_pipeline_allow_untrusted_secrets(self):
         self.pipeline.post_review = False
-        job = configloader.JobParser.fromYaml(self.tenant, self.layout, {
+        job_parser = configloader.JobParser(self.tenant, self.layout)
+        job = job_parser.fromYaml({
             '_source_context': self.context,
             '_start_mark': self.start_mark,
             'name': 'job',
diff --git a/zuul/configloader.py b/zuul/configloader.py
index 4745144..4f17074 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -542,8 +542,12 @@
         'override-checkout',
     ]
 
-    @staticmethod
-    def _getImpliedBranches(tenant, job):
+    def __init__(self, tenant, layout):
+        self.log = logging.getLogger("zuul.JobParser")
+        self.tenant = tenant
+        self.layout = layout
+
+    def _getImpliedBranches(self, job):
         # If the user has set a pragma directive for this, use the
         # value (if unset, the value is None).
         if job.source_context.implied_branch_matchers is True:
@@ -560,7 +564,7 @@
 
         # If this project only has one branch, don't create implied
         # branch matchers.  This way central job repos can work.
-        branches = tenant.getProjectBranches(job.source_context.project)
+        branches = self.tenant.getProjectBranches(job.source_context.project)
         if len(branches) == 1:
             return None
 
@@ -568,12 +572,11 @@
             return job.source_context.implied_branches
         return [job.source_context.branch]
 
-    @staticmethod
-    def fromYaml(tenant, layout, conf, project_pipeline=False,
-                 name=None, validate=True):
+    def fromYaml(self, conf, project_pipeline=False, name=None,
+                 validate=True):
         if validate:
             with configuration_exceptions('job', conf):
-                JobParser.schema(conf)
+                self.schema(conf)
 
         if name is None:
             name = conf['name']
@@ -607,10 +610,10 @@
         for secret_config in as_list(conf.get('secrets', [])):
             if isinstance(secret_config, str):
                 secret_name = secret_config
-                secret = layout.secrets.get(secret_name)
+                secret = self.layout.secrets.get(secret_name)
             else:
                 secret_name = secret_config['name']
-                secret = layout.secrets.get(secret_config['secret'])
+                secret = self.layout.secrets.get(secret_config['secret'])
             if secret is None:
                 raise SecretNotFoundError(secret_name)
             if secret_name == 'zuul' or secret_name == 'nodepool':
@@ -635,13 +638,13 @@
         if secrets and not conf['_source_context'].trusted:
             job.post_review = True
 
-        if conf.get('timeout') and tenant.max_job_timeout != -1 and \
-           int(conf['timeout']) > tenant.max_job_timeout:
-            raise MaxTimeoutError(job, tenant)
+        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('post-timeout') and tenant.max_job_timeout != -1 and \
-           int(conf['post-timeout']) > tenant.max_job_timeout:
-            raise MaxTimeoutError(job, 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 'post-review' in conf:
             if conf['post-review']:
@@ -656,13 +659,13 @@
         if 'roles' in conf:
             for role in conf.get('roles', []):
                 if 'zuul' in role:
-                    r = JobParser._makeZuulRole(tenant, job, role)
+                    r = self._makeZuulRole(job, role)
                     if r:
                         roles.append(r)
         # A job's repo should be an implicit role source for that job,
         # but not in a project-pipeline variant.
         if not project_pipeline:
-            r = JobParser._makeImplicitRole(job)
+            r = self._makeImplicitRole(job)
             roles.insert(0, r)
         job.addRoles(roles)
 
@@ -684,23 +687,23 @@
                                         job.roles, secrets)
             job.run = (run,)
 
-        for k in JobParser.simple_attributes:
+        for k in self.simple_attributes:
             a = k.replace('-', '_')
             if k in conf:
                 setattr(job, a, conf[k])
         if 'nodeset' in conf:
-            nodeset_parser = NodeSetParser(tenant, layout)
+            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 = layout.nodesets.get(conf_nodeset)
+                ns = self.layout.nodesets.get(conf_nodeset)
                 if ns is None:
                     raise NodesetNotFoundError(conf_nodeset)
             else:
                 ns = nodeset_parser.fromYaml(conf_nodeset, anonymous=True)
-            if tenant.max_nodes_per_job != -1 and \
-               len(ns) > tenant.max_nodes_per_job:
-                raise MaxNodeError(job, tenant)
+            if self.tenant.max_nodes_per_job != -1 and \
+               len(ns) > self.tenant.max_nodes_per_job:
+                raise MaxNodeError(job, self.tenant)
             job.nodeset = ns
 
         if 'required-projects' in conf:
@@ -716,7 +719,7 @@
                     project_name = project
                     project_override_branch = None
                     project_override_checkout = None
-                (trusted, project) = tenant.getProject(project_name)
+                (trusted, project) = self.tenant.getProject(project_name)
                 if project is None:
                     raise Exception("Unknown project %s" % (project_name,))
                 job_project = model.JobProject(project.canonical_name,
@@ -756,7 +759,7 @@
         if allowed_projects:
             allowed = []
             for p in as_list(allowed_projects):
-                (trusted, project) = tenant.getProject(p)
+                (trusted, project) = self.tenant.getProject(p)
                 if project is None:
                     raise Exception("Unknown project %s" % (p,))
                 allowed.append(project.name)
@@ -764,7 +767,7 @@
 
         branches = None
         if ('branches' not in conf):
-            branches = JobParser._getImpliedBranches(tenant, job)
+            branches = self._getImpliedBranches(job)
         if (not branches) and ('branches' in conf):
             branches = as_list(conf['branches'])
         if branches:
@@ -782,11 +785,10 @@
                 matchers)
         return job
 
-    @staticmethod
-    def _makeZuulRole(tenant, job, role):
+    def _makeZuulRole(self, job, role):
         name = role['zuul'].split('/')[-1]
 
-        (trusted, project) = tenant.getProject(role['zuul'])
+        (trusted, project) = self.tenant.getProject(role['zuul'])
         if project is None:
             return None
 
@@ -794,8 +796,7 @@
                               project.connection_name,
                               project.name)
 
-    @staticmethod
-    def _makeImplicitRole(job):
+    def _makeImplicitRole(self, job):
         project = job.source_context.project
         name = project.name.split('/')[-1]
         name = JobParser.ANSIBLE_ROLE_RE.sub('', name)
@@ -873,9 +874,9 @@
                                           attrs):
                 self.layout.getJob(jobname)
 
-            job_list.addJob(JobParser.fromYaml(self.tenant, self.layout,
-                                               attrs, project_pipeline=True,
-                                               name=jobname, validate=False))
+            job_parser = JobParser(self.tenant, self.layout)
+            job_list.addJob(job_parser.fromYaml(attrs, project_pipeline=True,
+                                                name=jobname, validate=False))
 
 
 class ProjectParser(object):
@@ -1640,12 +1641,13 @@
             with configuration_exceptions('secret', config_secret):
                 layout.addSecret(secret_parser.fromYaml(config_secret))
 
+        job_parser = JobParser(tenant, layout)
         for config_job in data.jobs:
             classes = TenantParser._getLoadClasses(tenant, config_job)
             if 'job' not in classes:
                 continue
             with configuration_exceptions('job', config_job):
-                job = JobParser.fromYaml(tenant, layout, config_job)
+                job = job_parser.fromYaml(config_job)
                 added = layout.addJob(job)
                 if not added:
                     TenantParser.log.debug(