Source code for flash_services.jenkins
"""Defines the Jenkins CI service integration."""
import logging
import time
from .auth import BasicAuthHeaderMixin
from .core import ContinuousIntegrationService, CustomRootMixin
from .utils import estimate_time, health_summary, naturaldelta
logger = logging.getLogger(__name__)
[docs]class Jenkins(BasicAuthHeaderMixin, CustomRootMixin,
ContinuousIntegrationService):
"""Show the current build status on a Jenkins instance.
Arguments:
username (:py:class:`str`): A valid username for the Jenkins
instance.
password (:py:class:`str`): A valid password for the Jenkins
instance.
root (:py:class:`str`): The root URL for the Jenkins instance.
job (:py:class:`str`): The name of the job (must match the job
URL).
"""
ENDPOINT = '/job/{job}/api/json'
OUTCOMES = {
None: 'working',
'WORKING': 'working',
'FAILURE': 'failed',
'UNSTABLE': 'failed',
'SUCCESS': 'passed',
'ABORTED': 'cancelled',
}
TREE_PARAMS = 'name,builds[building,timestamp,duration,result,description,changeSets[items[author[fullName],comment]]]' # pylint: disable=line-too-long
""":py:class:`str`: Definition of JSON tree to return."""
def __init__(self, *, job, **kwargs):
super().__init__(**kwargs)
self.job = job
@property
def url_params(self):
params = super().url_params
params['tree'] = self.TREE_PARAMS
return params
[docs] @classmethod
def format_data(cls, data):
"""Re-format the response data for the front-end.
Arguments:
data (:py:class:`dict`): The JSON data from the response.
Returns:
:py:class:`dict`: The re-formatted data.
"""
builds = [cls.format_build(build) for build in data['builds']]
estimate_time(builds)
return dict(
builds=builds[:4],
health=health_summary(builds),
name=data['name'],
)
[docs] @classmethod
def format_build(cls, build):
"""Re-format the build data for the front-end.
Arguments:
build (:py:class:`dict`): The JSON data from the response.
Returns:
:py:class:`dict`: The re-formatted data.
Note:
Jenkins returns timestamps in milliseconds, and always has a
zero ``duration`` for the in-progress build.
"""
started_at = build['timestamp'] // 1000
duration = (build['duration'] // 1000) or None
if build.get('building'):
build['result'] = 'WORKING'
if duration is not None:
elapsed = 'took {}'.format(naturaldelta(duration))
else:
duration = int(time.time()) - started_at
elapsed = 'elapsed time not available'
author, message = cls._extract_change(build)
return super().format_build(dict(
author=author,
duration=duration,
elapsed=elapsed,
message=message,
outcome=build['result'],
started_at=started_at,
))
@classmethod
def _extract_change(cls, build):
"""Extract the most recent change-set items from the build.
Arguments:
build (:py:class:`dict`): The JSON data from the response.
Returns:
:py:class:`tuple`: The author and message.
"""
changes = build.get('changeSets')
if not changes:
return None, None
items = changes[-1].get('items')
if not items:
return None, None
item = items[0]
return item.get('author', {}).get('fullName'), item.get('comment')