Merge "Prevent jobs from overriding jobs in other repos" into feature/zuulv3
This commit is contained in:
commit
960d1a2592
|
@ -17,6 +17,7 @@ import os
|
|||
import random
|
||||
|
||||
import fixtures
|
||||
import testtools
|
||||
|
||||
from zuul import model
|
||||
from zuul import configloader
|
||||
|
@ -55,19 +56,23 @@ class TestJob(BaseTestCase):
|
|||
pipeline = model.Pipeline('gate', layout)
|
||||
layout.addPipeline(pipeline)
|
||||
queue = model.ChangeQueue(pipeline)
|
||||
project = model.Project('project')
|
||||
|
||||
base = configloader.JobParser.fromYaml(layout, {
|
||||
'_source_project': project,
|
||||
'name': 'base',
|
||||
'timeout': 30,
|
||||
})
|
||||
layout.addJob(base)
|
||||
python27 = configloader.JobParser.fromYaml(layout, {
|
||||
'_source_project': project,
|
||||
'name': 'python27',
|
||||
'parent': 'base',
|
||||
'timeout': 40,
|
||||
})
|
||||
layout.addJob(python27)
|
||||
python27diablo = configloader.JobParser.fromYaml(layout, {
|
||||
'_source_project': project,
|
||||
'name': 'python27',
|
||||
'branches': [
|
||||
'stable/diablo'
|
||||
|
@ -86,7 +91,6 @@ class TestJob(BaseTestCase):
|
|||
})
|
||||
layout.addProjectConfig(project_config, update_pipeline=False)
|
||||
|
||||
project = model.Project('project')
|
||||
change = model.Change(project)
|
||||
change.branch = 'master'
|
||||
item = queue.enqueueChange(change)
|
||||
|
@ -122,19 +126,23 @@ class TestJob(BaseTestCase):
|
|||
pipeline = model.Pipeline('gate', layout)
|
||||
layout.addPipeline(pipeline)
|
||||
queue = model.ChangeQueue(pipeline)
|
||||
project = model.Project('project')
|
||||
|
||||
base = configloader.JobParser.fromYaml(layout, {
|
||||
'_source_project': project,
|
||||
'name': 'base',
|
||||
'timeout': 30,
|
||||
})
|
||||
layout.addJob(base)
|
||||
python27 = configloader.JobParser.fromYaml(layout, {
|
||||
'_source_project': project,
|
||||
'name': 'python27',
|
||||
'parent': 'base',
|
||||
'timeout': 40,
|
||||
})
|
||||
layout.addJob(python27)
|
||||
python27diablo = configloader.JobParser.fromYaml(layout, {
|
||||
'_source_project': project,
|
||||
'name': 'python27',
|
||||
'branches': [
|
||||
'stable/diablo'
|
||||
|
@ -153,7 +161,6 @@ class TestJob(BaseTestCase):
|
|||
})
|
||||
layout.addProjectConfig(project_config, update_pipeline=False)
|
||||
|
||||
project = model.Project('project')
|
||||
change = model.Change(project)
|
||||
change.branch = 'master'
|
||||
item = queue.enqueueChange(change)
|
||||
|
@ -183,6 +190,26 @@ class TestJob(BaseTestCase):
|
|||
self.assertEqual(job.name, 'python27')
|
||||
self.assertEqual(job.timeout, 70)
|
||||
|
||||
def test_job_source_project(self):
|
||||
layout = model.Layout()
|
||||
base_project = model.Project('base_project')
|
||||
base = configloader.JobParser.fromYaml(layout, {
|
||||
'_source_project': base_project,
|
||||
'name': 'base',
|
||||
})
|
||||
layout.addJob(base)
|
||||
|
||||
other_project = model.Project('other_project')
|
||||
base2 = configloader.JobParser.fromYaml(layout, {
|
||||
'_source_project': other_project,
|
||||
'name': 'base',
|
||||
})
|
||||
with testtools.ExpectedException(
|
||||
Exception,
|
||||
"Job base in other_project is not permitted "
|
||||
"to shadow job base in base_project"):
|
||||
layout.addJob(base2)
|
||||
|
||||
|
||||
class TestJobTimeData(BaseTestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -72,6 +72,7 @@ class JobParser(object):
|
|||
'irrelevant-files': to_list(str),
|
||||
'nodes': [node],
|
||||
'timeout': int,
|
||||
'_source_project': model.Project,
|
||||
}
|
||||
|
||||
return vs.Schema(job)
|
||||
|
@ -96,6 +97,10 @@ class JobParser(object):
|
|||
# accumulate onto any previously applied tags from
|
||||
# metajobs.
|
||||
job.tags = job.tags.union(set(tags))
|
||||
# This attribute may not be overridden -- it is always
|
||||
# supplied by the config loader and is the Project instance of
|
||||
# the repo where it originated.
|
||||
job.source_project = conf.get('_source_project')
|
||||
job.failure_message = conf.get('failure-message', job.failure_message)
|
||||
job.success_message = conf.get('success-message', job.success_message)
|
||||
job.failure_url = conf.get('failure-url', job.failure_url)
|
||||
|
@ -521,29 +526,29 @@ class TenantParser(object):
|
|||
(job.project, fn))
|
||||
if job.config_repo:
|
||||
incdata = TenantParser._parseConfigRepoLayout(
|
||||
job.files[fn])
|
||||
job.files[fn], job.project)
|
||||
config_repos_config.extend(incdata)
|
||||
else:
|
||||
incdata = TenantParser._parseProjectRepoLayout(
|
||||
job.files[fn])
|
||||
job.files[fn], job.project)
|
||||
project_repos_config.extend(incdata)
|
||||
job.project.unparsed_config = incdata
|
||||
return config_repos_config, project_repos_config
|
||||
|
||||
@staticmethod
|
||||
def _parseConfigRepoLayout(data):
|
||||
def _parseConfigRepoLayout(data, project):
|
||||
# This is the top-level configuration for a tenant.
|
||||
config = model.UnparsedTenantConfig()
|
||||
config.extend(yaml.load(data))
|
||||
config.extend(yaml.load(data), project)
|
||||
|
||||
return config
|
||||
|
||||
@staticmethod
|
||||
def _parseProjectRepoLayout(data):
|
||||
def _parseProjectRepoLayout(data, project):
|
||||
# TODOv3(jeblair): this should implement some rules to protect
|
||||
# aspects of the config that should not be changed in-repo
|
||||
config = model.UnparsedTenantConfig()
|
||||
config.extend(yaml.load(data))
|
||||
config.extend(yaml.load(data), project)
|
||||
|
||||
return config
|
||||
|
||||
|
@ -610,7 +615,7 @@ class ConfigLoader(object):
|
|||
data = project.unparsed_config
|
||||
if not data:
|
||||
continue
|
||||
incdata = TenantParser._parseProjectRepoLayout(data)
|
||||
incdata = TenantParser._parseProjectRepoLayout(data, project)
|
||||
config.extend(incdata)
|
||||
|
||||
layout = model.Layout()
|
||||
|
|
|
@ -525,6 +525,7 @@ class Job(object):
|
|||
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.project_source = None
|
||||
for k, v in self.attributes.items():
|
||||
setattr(self, k, v)
|
||||
|
||||
|
@ -1545,7 +1546,7 @@ class UnparsedTenantConfig(object):
|
|||
r.projects = copy.deepcopy(self.projects)
|
||||
return r
|
||||
|
||||
def extend(self, conf):
|
||||
def extend(self, conf, source_project=None):
|
||||
if isinstance(conf, UnparsedTenantConfig):
|
||||
self.pipelines.extend(conf.pipelines)
|
||||
self.jobs.extend(conf.jobs)
|
||||
|
@ -1570,6 +1571,8 @@ class UnparsedTenantConfig(object):
|
|||
if key == 'project':
|
||||
self.projects.append(value)
|
||||
elif key == 'job':
|
||||
if source_project is not None:
|
||||
value['_source_project'] = source_project
|
||||
self.jobs.append(value)
|
||||
elif key == 'project-template':
|
||||
self.project_templates.append(value)
|
||||
|
@ -1605,6 +1608,16 @@ class Layout(object):
|
|||
return self.jobs.get(name, [])
|
||||
|
||||
def addJob(self, job):
|
||||
# We can have multiple variants of a job all with the same
|
||||
# name, but these variants must all be defined in the same repo.
|
||||
prior_jobs = [j for j in self.getJobs(job.name)
|
||||
if j.source_project != job.source_project]
|
||||
if prior_jobs:
|
||||
raise Exception("Job %s in %s is not permitted to shadow "
|
||||
"job %s in %s" % (job, job.source_project,
|
||||
prior_jobs[0],
|
||||
prior_jobs[0].source_project))
|
||||
|
||||
if job.name in self.jobs:
|
||||
self.jobs[job.name].append(job)
|
||||
else:
|
||||
|
|
Loading…
Reference in New Issue