web: add /{tenant}/projects and /{tenant}/project/{project} routes

This change adds the projects_list and project_get gearman function to the
scheduler gear to expose projects configuration.

Co-Authored-By: Tristan Cacqueray <tdecacqu@redhat.com>
Change-Id: Idca04815027b75445961c77fa963b4e601a4d7d7
This commit is contained in:
Monty Taylor 2018-03-08 12:57:27 -06:00 committed by Joshua Hesketh
parent 13a2df3f12
commit 9944f14158
4 changed files with 237 additions and 0 deletions

View File

@ -371,6 +371,144 @@ class TestWeb(BaseTestWeb):
'voting': True
}], data)
def test_web_project_list(self):
# can we fetch the list of projects
data = self.get_url('api/tenant/tenant-one/projects').json()
expected_list = [
{'name': 'common-config', 'type': 'config'},
{'name': 'org/project', 'type': 'untrusted'},
{'name': 'org/project1', 'type': 'untrusted'},
{'name': 'org/project2', 'type': 'untrusted'}
]
for p in expected_list:
p["canonical_name"] = "review.example.com/%s" % p["name"]
p["connection_name"] = "gerrit"
self.assertEqual(expected_list, data)
def test_web_project_get(self):
# can we fetch project details
data = self.get_url(
'api/tenant/tenant-one/project/org/project1').json()
jobs = [[{'abstract': False,
'attempts': 3,
'branches': [],
'dependencies': [],
'description': None,
'files': [],
'final': False,
'implied_branch': None,
'irrelevant_files': [],
'name': 'project-merge',
'parent': 'base',
'post_review': None,
'protected': None,
'required_projects': [],
'roles': [],
'semaphore': None,
'source_context': {
'branch': 'master',
'path': 'zuul.yaml',
'project': 'common-config'},
'timeout': None,
'variables': {},
'variant_description': '',
'voting': True}],
[{'abstract': False,
'attempts': 3,
'branches': [],
'dependencies': ['project-merge'],
'description': None,
'files': [],
'final': False,
'implied_branch': None,
'irrelevant_files': [],
'name': 'project-test1',
'parent': 'base',
'post_review': None,
'protected': None,
'required_projects': [],
'roles': [],
'semaphore': None,
'source_context': {
'branch': 'master',
'path': 'zuul.yaml',
'project': 'common-config'},
'timeout': None,
'variables': {},
'variant_description': '',
'voting': True}],
[{'abstract': False,
'attempts': 3,
'branches': [],
'dependencies': ['project-merge'],
'description': None,
'files': [],
'final': False,
'implied_branch': None,
'irrelevant_files': [],
'name': 'project-test2',
'parent': 'base',
'post_review': None,
'protected': None,
'required_projects': [],
'roles': [],
'semaphore': None,
'source_context': {
'branch': 'master',
'path': 'zuul.yaml',
'project': 'common-config'},
'timeout': None,
'variables': {},
'variant_description': '',
'voting': True}],
[{'abstract': False,
'attempts': 3,
'branches': [],
'dependencies': ['project-merge'],
'description': None,
'files': [],
'final': False,
'implied_branch': None,
'irrelevant_files': [],
'name': 'project1-project2-integration',
'parent': 'base',
'post_review': None,
'protected': None,
'required_projects': [],
'roles': [],
'semaphore': None,
'source_context': {
'branch': 'master',
'path': 'zuul.yaml',
'project': 'common-config'},
'timeout': None,
'variables': {},
'variant_description': '',
'voting': True}]]
self.assertEqual(
{
'canonical_name': 'review.example.com/org/project1',
'connection_name': 'gerrit',
'name': 'org/project1',
'configs': [{
'templates': [],
'default_branch': 'master',
'merge_mode': 'merge-resolve',
'pipelines': [{
'name': 'check',
'queue_name': None,
'jobs': jobs,
}, {
'name': 'gate',
'queue_name': 'integrated',
'jobs': jobs,
}]
}]
}, data)
def test_web_keys(self):
with open(os.path.join(FIXTURE_DIR, 'public.pem'), 'rb') as f:
public_pem = f.read()

View File

@ -2815,6 +2815,11 @@ class ProjectPipelineConfig(ConfigObject):
# only come from templates.
self.variables = Job._deepUpdate(self.variables, other)
def toDict(self):
d = {}
d['queue_name'] = self.queue_name
return d
class ProjectConfig(ConfigObject):
# Represents a project configuration
@ -2865,6 +2870,17 @@ class ProjectConfig(ConfigObject):
return False
return True
def toDict(self):
d = {}
d['default_branch'] = self.default_branch
if self.merge_mode:
d['merge_mode'] = list(filter(lambda x: x[1] == self.merge_mode,
MERGER_MAP.items()))[0][0]
else:
d['merge_mode'] = None
d['templates'] = self.templates
return d
class ProjectMetadata(object):
"""Information about a Project

View File

@ -71,6 +71,8 @@ class RPCListener(object):
self.worker.registerFunction("zuul:status_get")
self.worker.registerFunction("zuul:job_get")
self.worker.registerFunction("zuul:job_list")
self.worker.registerFunction("zuul:project_get")
self.worker.registerFunction("zuul:project_list")
self.worker.registerFunction("zuul:key_get")
self.worker.registerFunction("zuul:config_errors_list")
@ -390,6 +392,55 @@ class RPCListener(object):
"description": desc})
job.sendWorkComplete(json.dumps(output))
def handle_project_get(self, gear_job):
args = json.loads(gear_job.arguments)
tenant = self.sched.abide.tenants.get(args["tenant"])
if not tenant:
gear_job.sendWorkComplete(json.dumps(None))
return
trusted, project = tenant.getProject(args["project"])
if not project:
gear_job.sendWorkComplete(json.dumps({}))
return
result = project.toDict()
result['configs'] = []
configs = tenant.layout.getAllProjectConfigs(project.canonical_name)
for config_obj in configs:
config = config_obj.toDict()
config['pipelines'] = []
for pipeline_name, pipeline_config in sorted(
config_obj.pipelines.items()):
pipeline = pipeline_config.toDict()
pipeline['name'] = pipeline_name
pipeline['jobs'] = []
for jobs in pipeline_config.job_list.jobs.values():
job_list = []
for job in jobs:
job_list.append(job.toDict(tenant))
pipeline['jobs'].append(job_list)
config['pipelines'].append(pipeline)
result['configs'].append(config)
gear_job.sendWorkComplete(json.dumps(result, cls=MappingProxyEncoder))
def handle_project_list(self, job):
args = json.loads(job.arguments)
tenant = self.sched.abide.tenants.get(args.get("tenant"))
if not tenant:
job.sendWorkComplete(json.dumps(None))
return
output = []
for project in tenant.config_projects:
pobj = project.toDict()
pobj['type'] = "config"
output.append(pobj)
for project in tenant.untrusted_projects:
pobj = project.toDict()
pobj['type'] = "untrusted"
output.append(pobj)
job.sendWorkComplete(json.dumps(
sorted(output, key=lambda project: project["name"])))
def handle_key_get(self, job):
args = json.loads(job.arguments)
tenant = self.sched.abide.tenants.get(args.get("tenant"))

View File

@ -301,6 +301,34 @@ class ZuulWebAPI(object):
resp.headers['Access-Control-Allow-Origin'] = '*'
return ret
@cherrypy.expose
@cherrypy.tools.save_params()
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
def projects(self, tenant):
job = self.rpc.submitJob('zuul:project_list', {'tenant': tenant})
ret = json.loads(job.data[0])
if ret is None:
raise cherrypy.HTTPError(404, 'Tenant %s does not exist.' % tenant)
resp = cherrypy.response
resp.headers['Access-Control-Allow-Origin'] = '*'
return ret
@cherrypy.expose
@cherrypy.tools.save_params()
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
def project(self, tenant, project):
job = self.rpc.submitJob(
'zuul:project_get', {'tenant': tenant, 'project': project})
ret = json.loads(job.data[0])
if ret is None:
raise cherrypy.HTTPError(404, 'Tenant %s does not exist.' % tenant)
if not ret:
raise cherrypy.HTTPError(
404, 'Project %s does not exist.' % project)
resp = cherrypy.response
resp.headers['Access-Control-Allow-Origin'] = '*'
return ret
@cherrypy.expose
@cherrypy.tools.save_params()
def key(self, tenant, project):
@ -527,6 +555,10 @@ class ZuulWeb(object):
controller=api, action='jobs')
route_map.connect('api', '/api/tenant/{tenant}/job/{job_name}',
controller=api, action='job')
route_map.connect('api', '/api/tenant/{tenant}/projects',
controller=api, action='projects')
route_map.connect('api', '/api/tenant/{tenant}/project/{project:.*}',
controller=api, action='project')
route_map.connect('api', '/api/tenant/{tenant}/key/{project:.*}.pub',
controller=api, action='key')
route_map.connect('api', '/api/tenant/{tenant}/'