web: add /{tenant}/jobs route

This change adds the 'job:list' job to the scheduler gearman worker
to expose the tenant jobs list.

This change also adds the /{tenant}/jobs.json endpoint to the zuul-web as well
as a /{tenant}/jobs.html web interface and command line client:
  zuul show jobs $tenant

Change-Id: I950cb6a809a360867b2daccded9a8a45ac46359c
This commit is contained in:
Tristan Cacqueray 2017-11-29 05:37:22 +00:00
parent 2623731dc6
commit be0441a839
6 changed files with 98 additions and 0 deletions

View File

@ -517,6 +517,7 @@ class JobParser(object):
# "job.run.append(...)").
job = model.Job(name)
job.description = conf.get('description')
job.source_context = conf.get('_source_context')
job.source_line = conf.get('_start_mark').line + 1

View File

@ -858,6 +858,7 @@ class Job(object):
source_line=None,
inheritance_path=(),
parent_data=None,
description=None,
)
self.inheritable_attributes = {}

View File

@ -58,6 +58,7 @@ class RPCListener(object):
self.worker.registerFunction("zuul:get_job_log_stream_address")
self.worker.registerFunction("zuul:tenant_list")
self.worker.registerFunction("zuul:status_get")
self.worker.registerFunction("zuul:job_list")
def getFunctions(self):
functions = {}
@ -283,3 +284,17 @@ class RPCListener(object):
args = json.loads(job.arguments)
output = self.sched.formatStatusJSON(args.get("tenant"))
job.sendWorkComplete(output)
def handle_job_list(self, job):
args = json.loads(job.arguments)
tenant = self.sched.abide.tenants.get(args.get("tenant"))
output = []
for job_name in sorted(tenant.layout.jobs):
desc = None
for tenant_job in tenant.layout.jobs[job_name]:
if tenant_job.description:
desc = tenant_job.description.split('\n')[0]
break
output.append({"name": job_name,
"description": desc})
job.sendWorkComplete(json.dumps(output))

View File

@ -162,6 +162,7 @@ class GearmanHandler(object):
self.controllers = {
'tenant_list': self.tenant_list,
'status_get': self.status_get,
'job_list': self.job_list,
}
def tenant_list(self, request):
@ -182,6 +183,11 @@ class GearmanHandler(object):
resp.last_modified = self.cache_time[tenant]
return resp
def job_list(self, request):
tenant = request.match_info["tenant"]
job = self.rpc.submitJob('zuul:job_list', {'tenant': tenant})
return web.json_response(json.loads(job.data[0]))
async def processRequest(self, request, action):
try:
resp = self.controllers[action](request)
@ -224,12 +230,17 @@ class ZuulWeb(object):
async def _handleStatusRequest(self, request):
return await self.gearman_handler.processRequest(request, 'status_get')
async def _handleJobsRequest(self, request):
return await self.gearman_handler.processRequest(request, 'job_list')
async def _handleStaticRequest(self, request):
fp = None
if request.path.endswith("tenants.html") or request.path.endswith("/"):
fp = os.path.join(STATIC_DIR, "index.html")
elif request.path.endswith("status.html"):
fp = os.path.join(STATIC_DIR, "status.html")
elif request.path.endswith("jobs.html"):
fp = os.path.join(STATIC_DIR, "jobs.html")
headers = {}
if self.static_cache_expiry:
headers['Cache-Control'] = "public, max-age=%d" % \
@ -251,7 +262,9 @@ class ZuulWeb(object):
('GET', '/console-stream', self._handleWebsocket),
('GET', '/tenants.json', self._handleTenantsRequest),
('GET', '/{tenant}/status.json', self._handleStatusRequest),
('GET', '/{tenant}/jobs.json', self._handleJobsRequest),
('GET', '/{tenant}/status.html', self._handleStaticRequest),
('GET', '/{tenant}/jobs.html', self._handleStaticRequest),
('GET', '/tenants.html', self._handleStaticRequest),
('GET', '/', self._handleStaticRequest),
]

View File

@ -30,3 +30,16 @@ angular.module('zuulTenants', []).controller(
}
$scope.tenants_fetch();
});
angular.module('zuulJobs', []).controller(
'mainController', function($scope, $http)
{
$scope.jobs = undefined;
$scope.jobs_fetch = function() {
$http.get("jobs.json")
.then(function success(result) {
$scope.jobs = result.data;
});
}
$scope.jobs_fetch();
});

55
zuul/web/static/jobs.html Normal file
View File

@ -0,0 +1,55 @@
<!--
Copyright 2017 Red Hat
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
-->
<!DOCTYPE html>
<html>
<head>
<title>Zuul Builds</title>
<link rel="stylesheet" href="/static/bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="../static/styles/zuul.css" />
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/angular.min.js"></script>
<script src="../static/javascripts/zuul.angular.js"></script>
</head>
<body ng-app="zuulJobs" ng-controller="mainController"><div class="container-fluid">
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="../" target="_self">Zuul Dashboard</a>
</div>
<ul class="nav navbar-nav">
<li><a href="status.html" target="_self">Status</a></li>
<li class="active"><a href="jobs.html" target="_self">Jobs</a></li>
<li><a href="builds.html" target="_self">Builds</a></li>
</ul>
</div>
</nav>
<table class="table table-hover table-condensed">
<thead>
<tr>
<th>Name</th>
<th>Description</th>
<th>Last builds</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="job in jobs">
<td>{{ job.name }}</td>
<td>{{ job.description }}</td>
<td><a href="builds.html?job_name={{ job.name }}">builds</a></td>
</tr>
</tbody>
</table>
</div></body></html>