Merge "Add multi-branch support for project-templates" into feature/zuulv3
diff --git a/tests/fixtures/config/central-jobs/git/central-jobs/README b/tests/fixtures/config/central-jobs/git/central-jobs/README
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/tests/fixtures/config/central-jobs/git/central-jobs/README
@@ -0,0 +1 @@
+test
diff --git a/tests/fixtures/config/central-jobs/git/central-jobs/playbooks/central-job.yaml b/tests/fixtures/config/central-jobs/git/central-jobs/playbooks/central-job.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/central-jobs/git/central-jobs/playbooks/central-job.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/central-jobs/git/central-jobs/zuul.yaml b/tests/fixtures/config/central-jobs/git/central-jobs/zuul.yaml
new file mode 100644
index 0000000..2bf782e
--- /dev/null
+++ b/tests/fixtures/config/central-jobs/git/central-jobs/zuul.yaml
@@ -0,0 +1,9 @@
+- job:
+ name: central-job
+ run: playbooks/central-job.yaml
+
+- project-template:
+ name: central-jobs
+ check:
+ jobs:
+ - central-job
diff --git a/tests/fixtures/config/central-jobs/git/common-config/zuul.yaml b/tests/fixtures/config/central-jobs/git/common-config/zuul.yaml
new file mode 100644
index 0000000..c31af45
--- /dev/null
+++ b/tests/fixtures/config/central-jobs/git/common-config/zuul.yaml
@@ -0,0 +1,52 @@
+- pipeline:
+ name: check
+ manager: independent
+ trigger:
+ gerrit:
+ - event: patchset-created
+ success:
+ gerrit:
+ Verified: 1
+ failure:
+ gerrit:
+ Verified: -1
+
+- pipeline:
+ name: gate
+ manager: dependent
+ trigger:
+ gerrit:
+ - event: comment-added
+ approval:
+ - Approved: 1
+ success:
+ gerrit:
+ Verified: 2
+ submit: true
+ failure:
+ gerrit:
+ Verified: -2
+ start:
+ gerrit:
+ Verified: 0
+ precedence: high
+
+- job:
+ name: base
+ parent: null
+
+- project:
+ name: common-config
+ check:
+ jobs: []
+ gate:
+ jobs:
+ - noop
+
+- project:
+ name: org/project
+ check:
+ jobs: []
+ gate:
+ jobs:
+ - noop
diff --git a/tests/fixtures/config/central-jobs/git/org_project/README b/tests/fixtures/config/central-jobs/git/org_project/README
new file mode 100644
index 0000000..9daeafb
--- /dev/null
+++ b/tests/fixtures/config/central-jobs/git/org_project/README
@@ -0,0 +1 @@
+test
diff --git a/tests/fixtures/config/central-jobs/main.yaml b/tests/fixtures/config/central-jobs/main.yaml
new file mode 100644
index 0000000..08f4d5d
--- /dev/null
+++ b/tests/fixtures/config/central-jobs/main.yaml
@@ -0,0 +1,9 @@
+- tenant:
+ name: tenant-one
+ source:
+ gerrit:
+ config-projects:
+ - common-config
+ untrusted-projects:
+ - central-jobs
+ - org/project
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index 80e6ccc..c04604d 100755
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -251,6 +251,94 @@
self.waitUntilSettled()
+class TestCentralJobs(ZuulTestCase):
+ tenant_config_file = 'config/central-jobs/main.yaml'
+
+ def setUp(self):
+ super(TestCentralJobs, self).setUp()
+ self.create_branch('org/project', 'stable')
+ self.fake_gerrit.addEvent(
+ self.fake_gerrit.getFakeBranchCreatedEvent(
+ 'org/project', 'stable'))
+ self.waitUntilSettled()
+
+ def _updateConfig(self, config, branch):
+ file_dict = {'.zuul.yaml': config}
+ C = self.fake_gerrit.addFakeChange('org/project', branch, 'C',
+ files=file_dict)
+ C.addApproval('Code-Review', 2)
+ self.fake_gerrit.addEvent(C.addApproval('Approved', 1))
+ self.waitUntilSettled()
+ self.fake_gerrit.addEvent(C.getChangeMergedEvent())
+ self.waitUntilSettled()
+
+ def _test_central_job_on_branch(self, branch, other_branch):
+ # Test that a job defined on a branchless repo only runs on
+ # the branch applied
+ config = textwrap.dedent(
+ """
+ - project:
+ name: org/project
+ check:
+ jobs:
+ - central-job
+ """)
+ self._updateConfig(config, branch)
+
+ A = self.fake_gerrit.addFakeChange('org/project', branch, 'A')
+ self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+ self.waitUntilSettled()
+
+ self.assertHistory([
+ dict(name='central-job', result='SUCCESS', changes='2,1')])
+
+ # No jobs should run for this change.
+ B = self.fake_gerrit.addFakeChange('org/project', other_branch, 'B')
+ self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
+ self.waitUntilSettled()
+
+ self.assertHistory([
+ dict(name='central-job', result='SUCCESS', changes='2,1')])
+
+ def test_central_job_on_stable(self):
+ self._test_central_job_on_branch('master', 'stable')
+
+ def test_central_job_on_master(self):
+ self._test_central_job_on_branch('stable', 'master')
+
+ def _test_central_template_on_branch(self, branch, other_branch):
+ # Test that a project-template defined on a branchless repo
+ # only runs on the branch applied
+ config = textwrap.dedent(
+ """
+ - project:
+ name: org/project
+ templates: ['central-jobs']
+ """)
+ self._updateConfig(config, branch)
+
+ A = self.fake_gerrit.addFakeChange('org/project', branch, 'A')
+ self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+ self.waitUntilSettled()
+
+ self.assertHistory([
+ dict(name='central-job', result='SUCCESS', changes='2,1')])
+
+ # No jobs should run for this change.
+ B = self.fake_gerrit.addFakeChange('org/project', other_branch, 'B')
+ self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
+ self.waitUntilSettled()
+
+ self.assertHistory([
+ dict(name='central-job', result='SUCCESS', changes='2,1')])
+
+ def test_central_template_on_stable(self):
+ self._test_central_template_on_branch('master', 'stable')
+
+ def test_central_template_on_master(self):
+ self._test_central_template_on_branch('stable', 'master')
+
+
class TestInRepoConfig(ZuulTestCase):
# A temporary class to hold new tests while others are disabled
@@ -357,6 +445,8 @@
dict(name='project-test2', result='SUCCESS', changes='2,1')])
def test_dynamic_template(self):
+ # Tests that a project can't update a template in another
+ # project.
in_repo_conf = textwrap.dedent(
"""
- job:
@@ -378,8 +468,12 @@
files=file_dict)
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
self.waitUntilSettled()
- self.assertHistory([
- dict(name='template-job', result='SUCCESS', changes='1,1')])
+
+ self.assertEqual(A.patchsets[0]['approvals'][0]['value'], "-1")
+ self.assertIn('Project template common-config-template '
+ 'is already defined',
+ A.messages[0],
+ "A should have failed the check pipeline")
def test_dynamic_config_non_existing_job(self):
"""Test that requesting a non existent job fails"""
diff --git a/zuul/configloader.py b/zuul/configloader.py
index ec2c1e0..01a87fd 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -477,12 +477,7 @@
]
@staticmethod
- def _getImpliedBranches(tenant, job, project_pipeline):
- # If this is a project pipeline, don't create implied branch
- # matchers -- that's handled in ProjectParser.
- if project_pipeline:
- return None
-
+ def _getImpliedBranches(tenant, 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:
@@ -673,8 +668,7 @@
branches = None
if ('branches' not in conf):
- branches = JobParser._getImpliedBranches(
- tenant, job, project_pipeline)
+ branches = JobParser._getImpliedBranches(tenant, job)
if (not branches) and ('branches' in conf):
branches = as_list(conf['branches'])
if branches:
@@ -745,8 +739,8 @@
if validate:
with configuration_exceptions('project-template', conf):
self.schema(conf)
- project_template = model.ProjectConfig(conf['name'])
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():
conf_pipeline = conf.get(pipeline.name)
@@ -1562,8 +1556,9 @@
classes = TenantParser._getLoadClasses(tenant, config_template)
if 'project-template' not in classes:
continue
- layout.addProjectTemplate(project_template_parser.fromYaml(
- config_template))
+ with configuration_exceptions('project-template', config_template):
+ layout.addProjectTemplate(project_template_parser.fromYaml(
+ config_template))
project_parser = ProjectParser(tenant, layout, project_template_parser)
for config_projects in data.projects.values():
diff --git a/zuul/model.py b/zuul/model.py
index 61c0c8a..cf63f64 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -1097,7 +1097,8 @@
if not job.branch_matcher and implied_branch:
job = job.copy()
job.setBranchMatcher([implied_branch])
- joblist.append(job)
+ if job not in joblist:
+ joblist.append(job)
class JobGraph(object):
@@ -2212,8 +2213,11 @@
class ProjectConfig(object):
# Represents a project cofiguration
- def __init__(self, name):
+ def __init__(self, name, source_context=None):
self.name = name
+ # If this is a template, it will have a source_context, but
+ # not if it is a project definition.
+ self.source_context = source_context
self.merge_mode = None
# The default branch for the project (usually master).
self.default_branch = None
@@ -2482,12 +2486,18 @@
self.pipelines[pipeline.name] = pipeline
def addProjectTemplate(self, project_template):
- if project_template.name in self.project_templates:
- # TODO(jeblair): issue a warning to the logs on loading
- # the config, and an error when this hits in a proposed
- # change.
- return
- self.project_templates[project_template.name] = project_template
+ template = self.project_templates.get(project_template.name)
+ if template:
+ if (project_template.source_context.project !=
+ template.source_context.project):
+ raise Exception("Project template %s is already defined" %
+ (project_template.name,))
+ for pipeline in project_template.pipelines:
+ template.pipelines[pipeline].job_list.\
+ inheritFrom(project_template.pipelines[pipeline].job_list,
+ None)
+ else:
+ self.project_templates[project_template.name] = project_template
def addProjectConfig(self, project_config):
self.project_configs[project_config.name] = project_config