summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTobias Henkel <tobias.henkel@bmw.de>2020-01-12 11:21:10 +0100
committerTobias Henkel <tobias.henkel@bmw.de>2020-05-21 06:57:19 +0000
commit5350525e65ac6a6e848865480c45ccccaeb74a5e (patch)
tree986d2c3fe213d113f477a7103c635e276e0cac13
parent551dbcbbc60edb4996563856e2b5f77d31b0bc4a (diff)
downloadzuul-5350525e65ac6a6e848865480c45ccccaeb74a5e.tar.gz
Support dynamic badges
Zuul currently has a zuul/gated badge that can be linked e.g. in a readme of a project. This is sufficient for many use cases. However if a project has periodic jobs that do extended testing which is not possible in check/gate this is not sufficient. For use cases like those we can add support for dynamic badges in zuul itself. Change-Id: I449fa9f38ca251522789b6075fbc876d21bd0200
-rw-r--r--doc/source/howtos/badges.rst7
-rw-r--r--releasenotes/notes/dynamic-badges-9b0e7a39e73775ba.yaml4
-rw-r--r--tests/unit/test_web.py33
-rw-r--r--web/public/failing.svg18
-rw-r--r--web/public/openapi.yaml38
-rw-r--r--web/public/passing.svg18
-rwxr-xr-xzuul/web/__init__.py23
7 files changed, 141 insertions, 0 deletions
diff --git a/doc/source/howtos/badges.rst b/doc/source/howtos/badges.rst
index e6613dfc8..10ff36c1d 100644
--- a/doc/source/howtos/badges.rst
+++ b/doc/source/howtos/badges.rst
@@ -16,3 +16,10 @@ report, it is a simple static file:
To use it, simply put ``https://zuul-ci.org/gated.svg`` into an RST or
markdown formatted README file, or use it in an ``<img>`` tag in HTML.
+
+For advanced usage Zuul also supports generating dynamic badges via the
+REST api. This can be useful if you want to display the status of e.g. periodic
+pipelines of a project. To use it use an url like
+``https://zuul.opendev.org/api/tenant/zuul/badge?project=zuul/zuul-website&pipeline=post``
+instead of the above mentioned url. It supports filtering by ``project``,
+``pipeline`` and ``branch``.
diff --git a/releasenotes/notes/dynamic-badges-9b0e7a39e73775ba.yaml b/releasenotes/notes/dynamic-badges-9b0e7a39e73775ba.yaml
new file mode 100644
index 000000000..33a76488e
--- /dev/null
+++ b/releasenotes/notes/dynamic-badges-9b0e7a39e73775ba.yaml
@@ -0,0 +1,4 @@
+---
+features:
+ - |
+ Support for generating dynamic :ref:`badges` has been added.
diff --git a/tests/unit/test_web.py b/tests/unit/test_web.py
index af968b1bc..381d4585a 100644
--- a/tests/unit/test_web.py
+++ b/tests/unit/test_web.py
@@ -1126,6 +1126,39 @@ class TestBuildInfo(ZuulDBTestCase, BaseTestWeb):
resp = self.get_url("api/tenant/non-tenant/builds")
self.assertEqual(404, resp.status_code)
+ def test_web_badge(self):
+ # Generate some build records in the db.
+ self.add_base_changes()
+ self.executor_server.hold_jobs_in_build = False
+ self.executor_server.release()
+ self.waitUntilSettled()
+
+ # Now request badge for the buildsets
+ result = self.get_url("api/tenant/tenant-one/badge")
+
+ self.log.error(result.content)
+ result.raise_for_status()
+ self.assertTrue(result.text.startswith('<svg '))
+ self.assertIn('passing', result.text)
+
+ # Generate a failing record
+ self.executor_server.hold_jobs_in_build = True
+ C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
+ C.addApproval('Code-Review', 2)
+ self.fake_gerrit.addEvent(C.addApproval('Approved', 1))
+ self.waitUntilSettled()
+ self.executor_server.failJob('project-merge', C)
+ self.executor_server.hold_jobs_in_build = False
+ self.executor_server.release()
+ self.waitUntilSettled()
+
+ # Request again badge for the buildsets
+ result = self.get_url("api/tenant/tenant-one/badge")
+ self.log.error(result.content)
+ result.raise_for_status()
+ self.assertTrue(result.text.startswith('<svg '))
+ self.assertIn('failing', result.text)
+
def test_web_list_buildsets(self):
# Generate some build records in the db.
self.add_base_changes()
diff --git a/web/public/failing.svg b/web/public/failing.svg
new file mode 100644
index 000000000..d6bc3cf95
--- /dev/null
+++ b/web/public/failing.svg
@@ -0,0 +1,18 @@
+<svg width="80.2" height="20" viewBox="0 0 802 200" xmlns="http://www.w3.org/2000/svg">
+ <linearGradient id="a" x2="0" y2="100%">
+ <stop offset="0" stop-opacity=".1" stop-color="#EEE"/>
+ <stop offset="1" stop-opacity=".1"/>
+ </linearGradient>
+ <mask id="m"><rect width="802" height="200" rx="30" fill="#FFF"/></mask>
+ <g mask="url(#m)">
+ <rect width="368" height="200" fill="#555"/>
+ <rect width="434" height="200" fill="#E43" x="368"/>
+ <rect width="802" height="200" fill="url(#a)"/>
+ </g>
+ <g fill="#fff" text-anchor="start" font-family="Verdana,DejaVu Sans,sans-serif" font-size="110">
+ <text x="60" y="148" textLength="268" fill="#000" opacity="0.25">build</text>
+ <text x="50" y="138" textLength="268">build</text>
+ <text x="423" y="148" textLength="334" fill="#000" opacity="0.25">failing</text>
+ <text x="413" y="138" textLength="334">failing</text>
+ </g>
+</svg>
diff --git a/web/public/openapi.yaml b/web/public/openapi.yaml
index 4338e5a43..6313d1263 100644
--- a/web/public/openapi.yaml
+++ b/web/public/openapi.yaml
@@ -6,6 +6,44 @@ openapi: 3.0.0
tags:
- name: tenant
paths:
+ /api/tenant/{tenant}/badge:
+ get:
+ operationId: get-badge
+ parameters:
+ - description: The tenant name
+ in: path
+ name: tenant
+ required: true
+ schema:
+ type: string
+ - description: A project name
+ in: query
+ name: project
+ schema:
+ type: string
+ - description: A pipeline name
+ in: query
+ name: pipeline
+ schema:
+ type: string
+ - description: A branch name
+ in: query
+ name: branch
+ schema:
+ type: string
+ responses:
+ '200':
+ content:
+ image/svg+xml:
+ schema:
+ description: SVG image
+ type: object
+ description: Badge describing the result of the latest buildset found.
+ '404':
+ description: No buildset found
+ summary: Get a badge describing the result of the latest buildset found.
+ tags:
+ - tenant
/api/tenant/{tenant}/builds:
get:
operationId: list-builds
diff --git a/web/public/passing.svg b/web/public/passing.svg
new file mode 100644
index 000000000..54cf11b43
--- /dev/null
+++ b/web/public/passing.svg
@@ -0,0 +1,18 @@
+<svg width="88.6" height="20" viewBox="0 0 886 200" xmlns="http://www.w3.org/2000/svg">
+ <linearGradient id="a" x2="0" y2="100%">
+ <stop offset="0" stop-opacity=".1" stop-color="#EEE"/>
+ <stop offset="1" stop-opacity=".1"/>
+ </linearGradient>
+ <mask id="m"><rect width="886" height="200" rx="30" fill="#FFF"/></mask>
+ <g mask="url(#m)">
+ <rect width="368" height="200" fill="#555"/>
+ <rect width="518" height="200" fill="#3C1" x="368"/>
+ <rect width="886" height="200" fill="url(#a)"/>
+ </g>
+ <g fill="#fff" text-anchor="start" font-family="Verdana,DejaVu Sans,sans-serif" font-size="110">
+ <text x="60" y="148" textLength="268" fill="#000" opacity="0.25">build</text>
+ <text x="50" y="138" textLength="268">build</text>
+ <text x="423" y="148" textLength="418" fill="#000" opacity="0.25">passing</text>
+ <text x="413" y="138" textLength="418">passing</text>
+ </g>
+</svg>
diff --git a/zuul/web/__init__.py b/zuul/web/__init__.py
index 6336ac15f..9f5c504ca 100755
--- a/zuul/web/__init__.py
+++ b/zuul/web/__init__.py
@@ -529,6 +529,7 @@ class ZuulWebAPI(object):
'project_ssh_key': '/api/tenant/{tenant}/project-ssh-key/'
'{project:.*}.pub',
'console_stream': '/api/tenant/{tenant}/console-stream',
+ 'badge': '/api/tenant/{tenant}/badge',
'builds': '/api/tenant/{tenant}/builds',
'build': '/api/tenant/{tenant}/build/{uuid}',
'buildsets': '/api/tenant/{tenant}/buildsets',
@@ -929,6 +930,26 @@ class ZuulWebAPI(object):
@cherrypy.expose
@cherrypy.tools.save_params()
+ def badge(self, tenant, project=None, pipeline=None, branch=None):
+ connection = self._get_connection(tenant)
+
+ buildsets = connection.getBuildsets(
+ tenant=tenant, project=project, pipeline=pipeline,
+ branch=branch, limit=1)
+ if not buildsets:
+ raise cherrypy.HTTPError(404, 'No buildset found')
+
+ if buildsets[0].result == 'SUCCESS':
+ file = 'passing.svg'
+ else:
+ file = 'failing.svg'
+ path = os.path.join(self.zuulweb.static_path, file)
+
+ return cherrypy.lib.static.serve_file(
+ path=path, content_type="image/svg+xml")
+
+ @cherrypy.expose
+ @cherrypy.tools.save_params()
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
def buildsets(self, tenant, project=None, pipeline=None, change=None,
branch=None, patchset=None, ref=None, newrev=None,
@@ -1179,6 +1200,8 @@ class ZuulWeb(object):
controller=api, action='console_stream')
route_map.connect('api', '/api/tenant/{tenant}/builds',
controller=api, action='builds')
+ route_map.connect('api', '/api/tenant/{tenant}/badge',
+ controller=api, action='badge')
route_map.connect('api', '/api/tenant/{tenant}/build/{uuid}',
controller=api, action='build')
route_map.connect('api', '/api/tenant/{tenant}/buildsets',