Merge "Report layout config errors for config repos" into feature/zuulv3
diff --git a/tests/fixtures/config/in-repo/git/common-config/playbooks/common-config-test.yaml b/tests/fixtures/config/in-repo/git/common-config/playbooks/common-config-test.yaml
new file mode 100644
index 0000000..f679dce
--- /dev/null
+++ b/tests/fixtures/config/in-repo/git/common-config/playbooks/common-config-test.yaml
@@ -0,0 +1,2 @@
+- hosts: all
+ tasks: []
diff --git a/tests/fixtures/config/in-repo/git/common-config/zuul.yaml b/tests/fixtures/config/in-repo/git/common-config/zuul.yaml
index 58b2051..d8b7200 100644
--- a/tests/fixtures/config/in-repo/git/common-config/zuul.yaml
+++ b/tests/fixtures/config/in-repo/git/common-config/zuul.yaml
@@ -35,3 +35,12 @@
gerrit:
verified: 0
precedence: high
+
+- job:
+ name: common-config-test
+
+- project:
+ name: common-config
+ tenant-one-gate:
+ jobs:
+ - common-config-test
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index cf88265..ea7e85a 100644
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -185,7 +185,7 @@
dict(name='project-test1', result='SUCCESS', changes='2,1'),
dict(name='project-test2', result='SUCCESS', changes='3,1')])
- def test_dynamic_syntax_error(self):
+ def test_untrusted_syntax_error(self):
in_repo_conf = textwrap.dedent(
"""
- job:
@@ -206,6 +206,27 @@
self.assertIn('syntax error', A.messages[1],
"A should have a syntax error reported")
+ def test_trusted_syntax_error(self):
+ in_repo_conf = textwrap.dedent(
+ """
+ - job:
+ name: project-test2
+ foo: error
+ """)
+
+ file_dict = {'zuul.yaml': in_repo_conf}
+ A = self.fake_gerrit.addFakeChange('common-config', 'master', 'A',
+ files=file_dict)
+ A.addApproval('code-review', 2)
+ self.fake_gerrit.addEvent(A.addApproval('approved', 1))
+ self.waitUntilSettled()
+
+ self.assertEqual(A.data['status'], 'NEW')
+ self.assertEqual(A.reported, 2,
+ "A should report start and failure")
+ self.assertIn('syntax error', A.messages[1],
+ "A should have a syntax error reported")
+
class TestAnsible(AnsibleZuulTestCase):
# A temporary class to hold new tests while others are disabled
diff --git a/zuul/configloader.py b/zuul/configloader.py
index 42616a8..50d6143 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -841,21 +841,41 @@
new_abide.tenants[tenant.name] = new_tenant
return new_abide
- def createDynamicLayout(self, tenant, files):
- config = tenant.config_repos_config.copy()
- for source, project in tenant.project_repos:
- for branch in source.getProjectBranches(project):
+ def _loadDynamicProjectData(self, config, source, project, files,
+ config_repo):
+ for branch in source.getProjectBranches(project):
+ data = None
+ if config_repo:
+ data = files.getFile(project.name, branch, 'zuul.yaml')
+ if not data:
data = files.getFile(project.name, branch, '.zuul.yaml')
- if data:
- source_context = model.SourceContext(project,
- branch, False)
- incdata = TenantParser._parseProjectRepoLayout(
+ if data:
+ source_context = model.SourceContext(project, branch,
+ config_repo)
+ if config_repo:
+ incdata = TenantParser._parseConfigRepoLayout(
data, source_context)
else:
- incdata = project.unparsed_branch_config[branch]
- if not incdata:
- continue
- config.extend(incdata)
+ incdata = TenantParser._parseProjectRepoLayout(
+ data, source_context)
+ else:
+ incdata = project.unparsed_branch_config.get(branch)
+ if not incdata:
+ continue
+ config.extend(incdata)
+
+ def createDynamicLayout(self, tenant, files, include_config_repos=False):
+ if include_config_repos:
+ config = model.UnparsedTenantConfig()
+ for source, project in tenant.config_repos:
+ self._loadDynamicProjectData(config, source, project,
+ files, True)
+ else:
+ config = tenant.config_repos_config.copy()
+ for source, project in tenant.project_repos:
+ self._loadDynamicProjectData(config, source, project,
+ files, False)
+
layout = model.Layout()
# TODOv3(jeblair): copying the pipelines could be dangerous/confusing.
layout.pipelines = tenant.layout.pipelines
diff --git a/zuul/manager/__init__.py b/zuul/manager/__init__.py
index 4447615..f0250cf 100644
--- a/zuul/manager/__init__.py
+++ b/zuul/manager/__init__.py
@@ -466,6 +466,43 @@
newrev=newrev,
)
+ def _loadDynamicLayout(self, item):
+ # Load layout
+ # Late import to break an import loop
+ import zuul.configloader
+ loader = zuul.configloader.ConfigLoader()
+
+ build_set = item.current_build_set
+ self.log.debug("Load dynamic layout with %s" % build_set.files)
+ try:
+ # First parse the config with as it will land with the
+ # full set of config and project repos. This lets us
+ # catch syntax errors in config repos even though we won't
+ # actually run with that config.
+ loader.createDynamicLayout(
+ item.pipeline.layout.tenant,
+ build_set.files,
+ include_config_repos=True)
+
+ # Then create the config a second time but without changes
+ # to config repos so that we actually use this config.
+ layout = loader.createDynamicLayout(
+ item.pipeline.layout.tenant,
+ build_set.files,
+ include_config_repos=False)
+ except zuul.configloader.ConfigurationSyntaxError as e:
+ self.log.info("Configuration syntax error "
+ "in dynamic layout %s" %
+ build_set.files)
+ item.setConfigError(str(e))
+ return None
+ except Exception:
+ self.log.exception("Error in dynamic layout %s" %
+ build_set.files)
+ item.setConfigError("Unknown configuration error")
+ return None
+ return layout
+
def getLayout(self, item):
if not item.change.updatesConfig():
if item.item_ahead:
@@ -479,27 +516,7 @@
if build_set.merge_state == build_set.COMPLETE:
if build_set.unable_to_merge:
return None
- # Load layout
- # Late import to break an import loop
- import zuul.configloader
- loader = zuul.configloader.ConfigLoader()
- self.log.debug("Load dynamic layout with %s" % build_set.files)
- try:
- layout = loader.createDynamicLayout(
- item.pipeline.layout.tenant,
- build_set.files)
- except zuul.configloader.ConfigurationSyntaxError as e:
- self.log.info("Configuration syntax error "
- "in dynamic layout %s" %
- build_set.files)
- item.setConfigError(str(e))
- return None
- except Exception:
- self.log.exception("Error in dynamic layout %s" %
- build_set.files)
- item.setConfigError("Unknown configuration error")
- return None
- return layout
+ return self._loadDynamicLayout(item)
build_set.merge_state = build_set.PENDING
self.log.debug("Preparing dynamic layout for: %s" % item.change)
dependent_items = self.getDependentItems(item)
@@ -508,7 +525,7 @@
merger_items = map(self._makeMergerItem, all_items)
self.sched.merger.mergeChanges(merger_items,
item.current_build_set,
- ['.zuul.yaml'],
+ ['zuul.yaml', '.zuul.yaml'],
self.pipeline.precedence)
def prepareLayout(self, item):