diff --git a/doc/source/howtos/badges.rst b/doc/source/howtos/badges.rst index e6613dfc81..10ff36c1d3 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 ```` 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 0000000000..33a76488ef --- /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 af968b1bc9..381d4585a2 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(' + + + + + + + + + + + + build + build + failing + failing + + diff --git a/web/public/openapi.yaml b/web/public/openapi.yaml index 4338e5a43b..6313d1263a 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 0000000000..54cf11b434 --- /dev/null +++ b/web/public/passing.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + build + build + passing + passing + + diff --git a/zuul/web/__init__.py b/zuul/web/__init__.py index 6336ac15f5..9f5c504ca4 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', @@ -927,6 +928,26 @@ class ZuulWebAPI(object): ret['builds'].append(self.buildToDict(build)) return ret + @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') @@ -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',