diff --git a/docs/conf.py b/docs/conf.py index d418983..02b153a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,6 +21,7 @@ import mock MOCK_MODULES = [ "alsaaudio", + 'circleci.api', "netifaces", "psutil", "lxml.html", "lxml.cssselect", "lxml", "praw", diff --git a/i3pystatus/circleci.py b/i3pystatus/circleci.py new file mode 100644 index 0000000..d774dd4 --- /dev/null +++ b/i3pystatus/circleci.py @@ -0,0 +1,155 @@ +import os + +import dateutil.parser + +from circleci.api import Api + +from i3pystatus import IntervalModule +from i3pystatus.core.util import TimeWrapper, formatp, internet, require + +__author__ = 'chestm007' + + +class CircleCI(IntervalModule): + """ + Get current status of circleci builds + Requires `circleci` `dateutil.parser` + + Formatters: + + * `{repo_slug}` - repository owner/repository name + * `{repo_status}` - repository status + * `{repo_name}` - repository name + * `{repo_owner}` - repository owner + * `{last_build_started}` - date of the last finished started + * `{last_build_duration}` - duration of the last build, not populated with workflows(yet) + + + Examples + + .. code-block:: python + + status_color_map = { + 'passed': '#00FF00', + 'failed': '#FF0000', + 'errored': '#FFAA00', + 'cancelled': '#EEEEEE', + 'started': '#0000AA', + + } + + .. code-block:: python + + repo_status_map={ + 'success': 'success', + 'running': 'running', + 'failed': 'failed', + } + + """ + + settings = ( + 'format', + ('circleci_token', 'circleci access token'), + ('repo_slug', 'repository identifier eg. "enkore/i3pystatus"'), + ('time_format', 'passed directly to .strftime() for `last_build_started`'), + ('repo_status_map', 'map representing how to display status'), + ('duration_format', '`last_build_duration` format string'), + ('status_color_map', 'color for all text based on status'), + ('color', 'color for all text not otherwise colored'), + ('workflow_name', '[WORKFLOWS_ONLY] if specified, monitor this workflows status. if not specified this module ' + 'will default to reporting the status of your last build'), + ('workflow_branch', '[WORKFLOWS_ONLY] if specified, monitor the workflows in this branch')) + + required = ('circleci_token', 'repo_slug') + + format = '{repo_owner}/{repo_name}-{repo_status} [({last_build_started}({last_build_duration}))]' + short_format = '{repo_name}-{repo_status}' + time_format = '%m/%d' + duration_format = '%m:%S' + status_color_map = None + repo_slug = None + circleci_token = None + repo_status_map = None + color = '#DDDDDD' + workflow_name = None + workflow_branch = None + + circleci = None + + on_leftclick = 'open_build_webpage' + + def init(self): + self.repo_status = None + self.last_build_duration = None + self.last_build_started = None + self.repo_owner, self.repo_name = self.repo_slug.split('/') + + self.workflows = self.workflow_name is not None or self.workflow_branch is not None + + def _format_time(self, time): + _datetime = dateutil.parser.parse(time) + return _datetime.strftime(self.time_format) + + @require(internet) + def run(self): + if self.circleci is None: + self.circleci = Api(self.circleci_token) + + if self.workflows: + if self.workflow_branch and not self.workflow_name: + self.output = dict( + full_text='workflow_name must be specified!' + ) + return + + project = {p['reponame']: p for p in self.circleci.get_projects()}.get(self.repo_name) + if not self.workflow_branch: + self.workflow_branch = project.get('default_branch') + + workflow_info = project['branches'].get(self.workflow_branch)['latest_workflows'].get(self.workflow_name) + + self.last_build_started = self._format_time(workflow_info.get('created_at')) + self.repo_status = workflow_info.get('status') + + self.last_build_duration = '' # TODO: gather this information once circleCI exposes it + + else: + self.repo_summary = self.circleci.get_project_build_summary( + self.repo_owner, + self.repo_name, + limit=1) + if len(self.repo_summary) != 1: + return + self.repo_summary = self.repo_summary[0] + + self.repo_status = self.repo_summary.get('status') + + self.last_build_started = self._format_time(self.repo_summary.get('queued_at')) + try: + self.last_build_duration = TimeWrapper( + self.repo_summary.get('build_time_millis') / 1000, + default_format=self.duration_format) + except TypeError: + self.last_build_duration = 0 + + if self.repo_status_map: + self.repo_status = self.repo_status_map.get(self.repo_status, self.repo_status) + + self.output = dict( + full_text=formatp(self.format, **vars(self)), + short_text=self.short_format.format(**vars(self)), + ) + if self.status_color_map: + self.output['color'] = self.status_color_map.get(self.repo_status, self.color) + else: + self.output['color'] = self.color + + def open_build_webpage(self): + if self.repo_summary.get('workflows'): + url_format = 'workflow-run/{}'.format(self.repo_summary['workflows']['workflow_id']) + else: + url_format = 'gh/{repo_owner}/{repo_name}/{job_number}' + + os.popen('xdg-open https:/circleci.com/{} > /dev/null' + .format(url_format))