Merge "Fix indentation on debug statement" into feature/zuulv3
diff --git a/doc/source/user/config.rst b/doc/source/user/config.rst
index 916e66a..fff673b 100644
--- a/doc/source/user/config.rst
+++ b/doc/source/user/config.rst
@@ -1032,11 +1032,12 @@
The following attributes may appear in a project:
.. attr:: name
- :required:
The name of the project. If Zuul is configured with two or more
unique projects with the same name, the canonical hostname for
the project should be included (e.g., `git.example.com/foo`).
+ If not given it is implicitly derived from the project where this
+ is defined.
.. attr:: templates
diff --git a/tests/fixtures/config/implicit-project/git/common-config/playbooks/test-common.yaml b/tests/fixtures/config/implicit-project/git/common-config/playbooks/test-common.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/implicit-project/git/common-config/playbooks/test-common.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/implicit-project/git/common-config/zuul.yaml b/tests/fixtures/config/implicit-project/git/common-config/zuul.yaml
new file mode 100644
index 0000000..038c412
--- /dev/null
+++ b/tests/fixtures/config/implicit-project/git/common-config/zuul.yaml
@@ -0,0 +1,57 @@
+- pipeline:
+ name: check
+ manager: independent
+ post-review: true
+ trigger:
+ gerrit:
+ - event: patchset-created
+ success:
+ gerrit:
+ Verified: 1
+ failure:
+ gerrit:
+ Verified: -1
+
+- pipeline:
+ name: gate
+ manager: dependent
+ success-message: Build succeeded (gate).
+ 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
+
+- job:
+ name: test-common
+ run: playbooks/test-common.yaml
+
+- project:
+ check:
+ jobs:
+ - test-common
+
+- project:
+ name: org/project
+ check:
+ jobs:
+ - test-common
+ gate:
+ jobs:
+ - test-common
diff --git a/tests/fixtures/config/implicit-project/git/org_project/.zuul.yaml b/tests/fixtures/config/implicit-project/git/org_project/.zuul.yaml
new file mode 100644
index 0000000..bce195c
--- /dev/null
+++ b/tests/fixtures/config/implicit-project/git/org_project/.zuul.yaml
@@ -0,0 +1,11 @@
+- job:
+ name: test-project
+ run: playbooks/test-project.yaml
+
+- project:
+ check:
+ jobs:
+ - test-project
+ gate:
+ jobs:
+ - test-project
diff --git a/tests/fixtures/config/implicit-project/git/org_project/playbooks/test-project.yaml b/tests/fixtures/config/implicit-project/git/org_project/playbooks/test-project.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/implicit-project/git/org_project/playbooks/test-project.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/implicit-project/main.yaml b/tests/fixtures/config/implicit-project/main.yaml
new file mode 100644
index 0000000..208e274
--- /dev/null
+++ b/tests/fixtures/config/implicit-project/main.yaml
@@ -0,0 +1,8 @@
+- tenant:
+ name: tenant-one
+ source:
+ gerrit:
+ config-projects:
+ - common-config
+ untrusted-projects:
+ - org/project
diff --git a/tests/unit/test_scheduler.py b/tests/unit/test_scheduler.py
index aacc81e..6bbf098 100755
--- a/tests/unit/test_scheduler.py
+++ b/tests/unit/test_scheduler.py
@@ -6070,6 +6070,77 @@
self.assertEqual(B.reported, 1)
+class TestImplicitProject(ZuulTestCase):
+ tenant_config_file = 'config/implicit-project/main.yaml'
+
+ def test_implicit_project(self):
+ # config project should work with implicit project name
+ A = self.fake_gerrit.addFakeChange('common-config', 'master', 'A')
+ self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+
+ # untrusted project should work with implicit project name
+ B = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+ self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
+
+ self.waitUntilSettled()
+
+ self.assertEqual(A.data['status'], 'NEW')
+ self.assertEqual(A.reported, 1)
+ self.assertEqual(B.data['status'], 'NEW')
+ self.assertEqual(B.reported, 1)
+ self.assertHistory([
+ dict(name='test-common', result='SUCCESS', changes='1,1'),
+ dict(name='test-common', result='SUCCESS', changes='2,1'),
+ dict(name='test-project', result='SUCCESS', changes='2,1'),
+ ], ordered=False)
+
+ # now test adding a further project in repo
+ in_repo_conf = textwrap.dedent(
+ """
+ - job:
+ name: test-project
+ run: playbooks/test-project.yaml
+ - job:
+ name: test2-project
+ run: playbooks/test-project.yaml
+
+ - project:
+ check:
+ jobs:
+ - test-project
+ gate:
+ jobs:
+ - test-project
+
+ - project:
+ check:
+ jobs:
+ - test2-project
+ gate:
+ jobs:
+ - test2-project
+
+ """)
+ file_dict = {'.zuul.yaml': in_repo_conf}
+ C = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
+ files=file_dict)
+ C.addApproval('Code-Review', 2)
+ self.fake_gerrit.addEvent(C.addApproval('Approved', 1))
+ self.waitUntilSettled()
+
+ # change C must be merged
+ self.assertEqual(C.data['status'], 'MERGED')
+ self.assertEqual(C.reported, 2)
+ self.assertHistory([
+ dict(name='test-common', result='SUCCESS', changes='1,1'),
+ dict(name='test-common', result='SUCCESS', changes='2,1'),
+ dict(name='test-project', result='SUCCESS', changes='2,1'),
+ dict(name='test-common', result='SUCCESS', changes='3,1'),
+ dict(name='test-project', result='SUCCESS', changes='3,1'),
+ dict(name='test2-project', result='SUCCESS', changes='3,1'),
+ ], ordered=False)
+
+
class TestSemaphoreInRepo(ZuulTestCase):
config_file = 'zuul-connections-gerrit-and-github.conf'
tenant_config_file = 'config/in-repo/main.yaml'
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index 44aa966..2779e6e 100755
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -543,11 +543,23 @@
name: project-test2
run: playbooks/project-test2.yaml
+ - job:
+ name: project-test3
+ run: playbooks/project-test2.yaml
+
+ # add a job by the short project name
- project:
name: org/project
tenant-one-gate:
jobs:
- project-test2
+
+ # add a job by the canonical project name
+ - project:
+ name: review.example.com/org/project
+ tenant-one-gate:
+ jobs:
+ - project-test3
""")
in_repo_playbook = textwrap.dedent(
@@ -569,7 +581,9 @@
self.assertIn('tenant-one-gate', A.messages[1],
"A should transit tenant-one gate")
self.assertHistory([
- dict(name='project-test2', result='SUCCESS', changes='1,1')])
+ dict(name='project-test2', result='SUCCESS', changes='1,1'),
+ dict(name='project-test3', result='SUCCESS', changes='1,1'),
+ ], ordered=False)
self.fake_gerrit.addEvent(A.getChangeMergedEvent())
self.waitUntilSettled()
@@ -584,7 +598,10 @@
'SUCCESS')
self.assertHistory([
dict(name='project-test2', result='SUCCESS', changes='1,1'),
- dict(name='project-test2', result='SUCCESS', changes='2,1')])
+ dict(name='project-test3', result='SUCCESS', changes='1,1'),
+ dict(name='project-test2', result='SUCCESS', changes='2,1'),
+ dict(name='project-test3', result='SUCCESS', changes='2,1'),
+ ], ordered=False)
def test_dynamic_template(self):
# Tests that a project can't update a template in another
diff --git a/zuul/configloader.py b/zuul/configloader.py
index 71c4ccc..3a7e9b9 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -852,7 +852,7 @@
def getSchema(self):
project = {
- vs.Required('name'): str,
+ 'name': str,
'description': str,
'templates': [str],
'merge-mode': vs.Any('merge', 'merge-resolve',
@@ -1228,8 +1228,8 @@
tenant.config_projects,
tenant.untrusted_projects,
cached, tenant)
- unparsed_config.extend(tenant.config_projects_config, tenant=tenant)
- unparsed_config.extend(tenant.untrusted_projects_config, tenant=tenant)
+ unparsed_config.extend(tenant.config_projects_config, tenant)
+ unparsed_config.extend(tenant.untrusted_projects_config, tenant)
tenant.layout = TenantParser._parseLayout(base, tenant,
unparsed_config,
scheduler,
@@ -1484,10 +1484,10 @@
(job.project,))
if job.config_project:
config_projects_config.extend(
- job.project.unparsed_config)
+ job.project.unparsed_config, tenant)
else:
untrusted_projects_config.extend(
- job.project.unparsed_config)
+ job.project.unparsed_config, tenant)
continue
TenantParser.log.debug("Waiting for cat job %s" % (job,))
job.wait()
@@ -1518,17 +1518,18 @@
branch = source_context.branch
if source_context.trusted:
incdata = TenantParser._parseConfigProjectLayout(
- job.files[fn], source_context)
- config_projects_config.extend(incdata)
+ job.files[fn], source_context, tenant)
+ config_projects_config.extend(incdata, tenant)
else:
incdata = TenantParser._parseUntrustedProjectLayout(
- job.files[fn], source_context)
- untrusted_projects_config.extend(incdata)
- new_project_unparsed_config[project].extend(incdata)
+ job.files[fn], source_context, tenant)
+ untrusted_projects_config.extend(incdata, tenant)
+ new_project_unparsed_config[project].extend(
+ incdata, tenant)
if branch in new_project_unparsed_branch_config.get(
project, {}):
new_project_unparsed_branch_config[project][branch].\
- extend(incdata)
+ extend(incdata, tenant)
# 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():
@@ -1540,18 +1541,18 @@
return config_projects_config, untrusted_projects_config
@staticmethod
- def _parseConfigProjectLayout(data, source_context):
+ def _parseConfigProjectLayout(data, source_context, tenant):
# 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))
+ config.extend(safe_load_yaml(data, source_context), tenant)
return config
@staticmethod
- def _parseUntrustedProjectLayout(data, source_context):
+ def _parseUntrustedProjectLayout(data, source_context, tenant):
config = model.UnparsedTenantConfig()
with early_configuration_exceptions(source_context):
- config.extend(safe_load_yaml(data, source_context))
+ config.extend(safe_load_yaml(data, source_context), tenant)
if config.pipelines:
with configuration_exceptions('pipeline', config.pipelines[0]):
raise PipelineNotPermittedError()
@@ -1753,7 +1754,7 @@
else:
incdata = project.unparsed_branch_config.get(branch)
if incdata:
- config.extend(incdata)
+ config.extend(incdata, tenant)
continue
# Otherwise, do not use the cached config (even if the
# files are empty as that likely means they were deleted).
@@ -1782,12 +1783,12 @@
if trusted:
incdata = TenantParser._parseConfigProjectLayout(
- data, source_context)
+ data, source_context, tenant)
else:
incdata = TenantParser._parseUntrustedProjectLayout(
- data, source_context)
+ data, source_context, tenant)
- config.extend(incdata)
+ config.extend(incdata, tenant)
def createDynamicLayout(self, tenant, files,
include_config_projects=False,
diff --git a/zuul/model.py b/zuul/model.py
index ae98b7d..cc2fea7 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -2256,7 +2256,7 @@
class ProjectConfig(object):
- # Represents a project cofiguration
+ # Represents a project configuration
def __init__(self, name, source_context=None):
self.name = name
# If this is a template, it will have a source_context, but
@@ -2401,7 +2401,7 @@
r.semaphores = copy.deepcopy(self.semaphores)
return r
- def extend(self, conf, tenant=None):
+ def extend(self, conf, tenant):
if isinstance(conf, UnparsedTenantConfig):
self.pragmas.extend(conf.pragmas)
self.pipelines.extend(conf.pipelines)
@@ -2409,16 +2409,14 @@
self.project_templates.extend(conf.project_templates)
for k, v in conf.projects.items():
name = k
- # If we have the tenant 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
+ # 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.
- if tenant is not None:
- trusted, project = tenant.getProject(k)
- if project is not None:
- name = project.canonical_name
+ trusted, project = tenant.getProject(k)
+ if project is not None:
+ name = project.canonical_name
self.projects.setdefault(name, []).extend(v)
self.nodesets.extend(conf.nodesets)
self.secrets.extend(conf.secrets)
@@ -2435,7 +2433,12 @@
raise ConfigItemMultipleKeysError()
key, value = list(item.items())[0]
if key == 'project':
- name = value['name']
+ 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)
elif key == 'job':
self.jobs.append(value)