From b920927dabc87ac442721328eb7b13165914b7aa Mon Sep 17 00:00:00 2001 From: Fabio Manganiello Date: Sun, 12 Jan 2020 13:53:46 +0100 Subject: [PATCH] Added Travis-Ci integration (closes #104) --- docs/source/backends.rst | 1 + docs/source/events.rst | 1 + docs/source/platypush/backend/travisci.rst | 5 ++ docs/source/platypush/events/travisci.rst | 5 ++ docs/source/platypush/plugins/travisci.rst | 5 ++ docs/source/plugins.rst | 1 + platypush/backend/travisci.py | 77 ++++++++++++++++++++++ platypush/message/event/travisci.py | 68 +++++++++++++++++++ platypush/plugins/travisci.py | 58 ++++++++++++++++ 9 files changed, 221 insertions(+) create mode 100644 docs/source/platypush/backend/travisci.rst create mode 100644 docs/source/platypush/events/travisci.rst create mode 100644 docs/source/platypush/plugins/travisci.rst create mode 100644 platypush/backend/travisci.py create mode 100644 platypush/message/event/travisci.py create mode 100644 platypush/plugins/travisci.py diff --git a/docs/source/backends.rst b/docs/source/backends.rst index ca3b548ad..312cf45a7 100644 --- a/docs/source/backends.rst +++ b/docs/source/backends.rst @@ -53,6 +53,7 @@ Backends platypush/backend/sensor.serial.rst platypush/backend/tcp.rst platypush/backend/todoist.rst + platypush/backend/travisci.rst platypush/backend/trello.rst platypush/backend/weather.buienradar.rst platypush/backend/weather.darksky.rst diff --git a/docs/source/events.rst b/docs/source/events.rst index dc496513a..398be9682 100644 --- a/docs/source/events.rst +++ b/docs/source/events.rst @@ -46,6 +46,7 @@ Events platypush/events/sound.rst platypush/events/todoist.rst platypush/events/torrent.rst + platypush/events/travisci.rst platypush/events/trello.rst platypush/events/video.rst platypush/events/weather.rst diff --git a/docs/source/platypush/backend/travisci.rst b/docs/source/platypush/backend/travisci.rst new file mode 100644 index 000000000..940c44307 --- /dev/null +++ b/docs/source/platypush/backend/travisci.rst @@ -0,0 +1,5 @@ +``platypush.backend.travisci`` +============================== + +.. automodule:: platypush.backend.travisci + :members: diff --git a/docs/source/platypush/events/travisci.rst b/docs/source/platypush/events/travisci.rst new file mode 100644 index 000000000..a5e557d91 --- /dev/null +++ b/docs/source/platypush/events/travisci.rst @@ -0,0 +1,5 @@ +``platypush.message.event.travisci`` +==================================== + +.. automodule:: platypush.message.event.travisci + :members: diff --git a/docs/source/platypush/plugins/travisci.rst b/docs/source/platypush/plugins/travisci.rst new file mode 100644 index 000000000..7503e701a --- /dev/null +++ b/docs/source/platypush/plugins/travisci.rst @@ -0,0 +1,5 @@ +``platypush.plugins.travisci`` +============================== + +.. automodule:: platypush.plugins.travisci + :members: diff --git a/docs/source/plugins.rst b/docs/source/plugins.rst index 585d009ba..c5c318c48 100644 --- a/docs/source/plugins.rst +++ b/docs/source/plugins.rst @@ -96,6 +96,7 @@ Plugins platypush/plugins/tcp.rst platypush/plugins/todoist.rst platypush/plugins/torrent.rst + platypush/plugins/travisci.rst platypush/plugins/trello.rst platypush/plugins/tts.rst platypush/plugins/tts.google.rst diff --git a/platypush/backend/travisci.py b/platypush/backend/travisci.py new file mode 100644 index 000000000..d1946566d --- /dev/null +++ b/platypush/backend/travisci.py @@ -0,0 +1,77 @@ +from typing import Optional + +from platypush.backend import Backend +from platypush.context import get_plugin +from platypush.message.event.travisci import TravisciBuildPassedEvent, TravisciBuildFailedEvent + + +class TravisciBackend(Backend): + """ + This backend polls for new builds on a `Travis-Ci `_ account and triggers an event + whenever a new build is completed. + + Requires: + + * The :class:`platypush.plugins.foursquare.FoursquarePlugin` plugin configured and enabled. + + Triggers: + + - :class:`platypush.message.event.foursquare.FoursquareCheckinEvent` when a new check-in occurs. + + """ + + _last_build_finished_at_varname = '_travisci_last_build_finished_at' + + def __init__(self, poll_seconds: Optional[float] = 60.0, *args, **kwargs): + """ + :param poll_seconds: How often the backend should check for new builds (default: one minute). + """ + super().__init__(*args, poll_seconds=poll_seconds, **kwargs) + self._last_build_finished_at = None + + def __enter__(self): + self._last_build_finished_at = int(get_plugin('variable').get(self._last_build_finished_at_varname). + output.get(self._last_build_finished_at_varname) or 0) + self.logger.info('Started Travis-CI backend') + + def loop(self): + builds = get_plugin('travisci').builds(limit=1).output + if not builds: + return + + last_build = builds[0] + last_build_finished_at = last_build.get('finished_at', 0) + if self._last_build_finished_at and last_build_finished_at <= self._last_build_finished_at: + return + + if last_build.get('state') == 'passed': + evt_type = TravisciBuildPassedEvent + elif last_build.get('state') == 'failed': + evt_type = TravisciBuildFailedEvent + else: + return + + evt = evt_type(repository_id=last_build.get('repository', {}).get('id'), + repository_name=last_build.get('repository', {}).get('name'), + repository_slug=last_build.get('repository').get('slug'), + build_id=int(last_build.get('id')), + build_number=int(last_build.get('number')), + duration=last_build.get('duration'), + previous_state=last_build.get('previous_state'), + private=last_build.get('private'), + tag=last_build.get('tag'), + branch=last_build.get('branch', {}).get('name'), + commit_id=last_build.get('commit', {}).get('id'), + commit_sha=last_build.get('commit', {}).get('sha'), + commit_message=last_build.get('commit', {}).get('message'), + committed_at=last_build.get('commit', {}).get('committed_at'), + created_by=last_build.get('created_by', {}).get('login'), + started_at=last_build.get('started_at'), + finished_at=last_build.get('finished_at')) + + self.bus.post(evt) + self._last_build_finished_at = last_build_finished_at + get_plugin('variable').set(**{self._last_build_finished_at_varname: self._last_build_finished_at}) + + +# vim:sw=4:ts=4:et: diff --git a/platypush/message/event/travisci.py b/platypush/message/event/travisci.py new file mode 100644 index 000000000..693a7cbd1 --- /dev/null +++ b/platypush/message/event/travisci.py @@ -0,0 +1,68 @@ +from typing import Optional + +from platypush.message.event import Event + + +class TravisciBuildEvent(Event): + def __init__(self, + repository_id: int, + repository_name: str, + repository_slug: str, + passed: bool, + build_id: int, + build_number: int, + duration: int, + previous_state: str, + private: bool, + tag: Optional[str], + branch: str, + commit_id: Optional[str], + commit_sha: Optional[str], + commit_message: Optional[str], + committed_at: str, + created_by: str, + started_at: str, + finished_at: str, + *args, + **kwargs): + super().__init__(*args, + repository_id=repository_id, + repository_name=repository_name, + repository_slug=repository_slug, + passed=passed, + build_id=build_id, + build_number=build_number, + duration=duration, + previous_state=previous_state, + private=private, + tag=tag, + branch=branch, + commit_id=commit_id, + commit_sha=commit_sha, + commit_message=commit_message, + committed_at=committed_at, + created_by=created_by, + started_at=started_at, + finished_at=finished_at, + **kwargs) + + +class TravisciBuildPassedEvent(TravisciBuildEvent): + """ + Event triggered when a Travis-Ci build passes. + """ + def __init__(self, *args, **kwargs): + kwargs['passed'] = True + super().__init__(*args, **kwargs) + + +class TravisciBuildFailedEvent(TravisciBuildEvent): + """ + Event triggered when a Travis-Ci build fails. + """ + def __init__(self, *args, **kwargs): + kwargs['passed'] = False + super().__init__(*args, **kwargs) + + +# vim:sw=4:ts=4:et: diff --git a/platypush/plugins/travisci.py b/platypush/plugins/travisci.py new file mode 100644 index 000000000..64d9e3358 --- /dev/null +++ b/platypush/plugins/travisci.py @@ -0,0 +1,58 @@ +import requests +from typing import Callable, Dict, Any, List, Optional + +from platypush.plugins import Plugin, action + + +class TravisciPlugin(Plugin): + """ + `Travis-Ci `_ continuous integration plugin. + + Setup + ##### + + - Get your API token from your `Travis-Ci account settings page `_. + + """ + + api_base_url = 'https://api.travis-ci.org/' + + def __init__(self, token: str, **kwargs): + super().__init__(**kwargs) + self.headers = { + 'Travis-API-Version': '3', + 'Authorization': 'token ' + token, + } + + def _make_request(self, method: Callable, endpoint: str, **kwargs): + url = self.api_base_url + endpoint + response = method(url, headers=self.headers, **kwargs).json() + if response.get('@type') == 'error': + raise AssertionError('{type}: {message}'. + format(type=response.get('error_type'), message=response.get('error_message'))) + + return response + + @action + def repos(self) -> Dict[str, Dict[str, Any]]: + """ + Get the repos owned by current user. + :return: Repo name -> Repo attributes mapping. + """ + return { + repo['name']: repo + for repo in self._make_request(requests.get, endpoint='repos').get('repositories', []) + } + + @action + def builds(self, limit: int = 100) -> Dict[str, List[Dict[str, Any]]]: + """ + Get the list of builds triggered on the owned repositories + + :param limit: Maximum number of builds to be retrieved (default: 100). + :return: Repo name -> List of builds + """ + return self._make_request(requests.get, endpoint='builds').get('builds', [])[:limit] + + +# vim:sw=4:ts=4:et: