Source code for estuary_updater.handlers.base

# SPDX-License-Identifier: GPL-3.0+

from __future__ import unicode_literals, absolute_import

import abc
from datetime import datetime

import neomodel
import koji
from estuary.models.koji import KojiBuild, ContainerKojiBuild, ModuleKojiBuild
from estuary.models.user import User

from estuary_updater import log


[docs]class BaseHandler(object): """An abstract base class for handlers to enforce the API.""" __metaclass__ = abc.ABCMeta def __init__(self, config): """ Initialize the handler. :param dict config: the fedmsg configuration """ self._koji_session = None self.config = config if config.get('estuary_updater.neo4j_url'): neomodel.config.DATABASE_URL = config['estuary_updater.neo4j_url'] else: log.warn('The configuration "estuary_updater.neo4j_url" was not set, so the default ' 'will be used') neomodel.config.DATABASE_URL = 'bolt://neo4j:neo4j@localhost:7687'
[docs] @staticmethod @abc.abstractmethod def can_handle(msg): """ Determine if this handler can handle this message. :param dict msg: a message to be analyzed :return: a bool based on if the handler can handle this kind of message :rtype: bool """ pass
[docs] @abc.abstractmethod def handle(self, msg): """ Handle a message and update Neo4j if necessary. :param dict msg: a message to be processed """ pass
@property def koji_session(self): """ Get a cached Koji session but initialize the connection first if needed. :return: a Koji session object :rtype: koji.ClientSession """ if not self._koji_session: self._koji_session = koji.ClientSession(self.config['estuary_updater.koji_url']) return self._koji_session
[docs] def is_container_build(self, build_info): """ Check whether a Koji build is a container build. :param KojiBuild build_info: build info from the Koji API :return: boolean value indicating whether the build is a container build :rtype: bool """ pkg = build_info['package_name'] extra = build_info.get('extra') # Checking heuristics for determining if a build is a container build, since currently # there is no definitive way to do it. if not extra: return False if extra.get('container_koji_build_id') or extra.get('container_koji_task_id'): return True elif extra.get('image') and (pkg.endswith('-container') or pkg.endswith('-docker')): return True return False
[docs] def is_module_build(self, build_info): """ Check whether a Koji build is a module build. :param KojiBuild build_info: build info from Koji API :return: boolean value indicating whether the build is a module build :rtype: bool """ build_extra = build_info.get('extra', {}) if not build_extra: return False return bool(build_extra.get('typeinfo', {}).get('module'))
[docs] def get_or_create_build(self, identifier, original_nvr=None, force_container_label=False): """ Get a Koji build from Neo4j, or create it if it does not exist in Neo4j. :param str/int identifier: an NVR (str) or build ID (int), or a dict of info from Koji API :kwarg str original_nvr: original_nvr property for the ContainerKojiBuild :kwarg bool force_container_label: when true, this skips the check to see if the build is a container and just creates the build with the ContainerKojiBuild label :rtype: KojiBuild :return: the Koji Build retrieved or created from Neo4j """ if type(identifier) is dict: build_info = identifier else: try: build_info = self.koji_session.getBuild(identifier, strict=True) except Exception: log.error('Failed to get brew build using the identifier {0}'.format(identifier)) raise build_params = { 'epoch': build_info['epoch'], 'id_': str(build_info['id']), 'name': build_info['package_name'], 'release': build_info['release'], 'state': build_info['state'], 'version': build_info['version'] } # To handle the case when a message has a null timestamp for time_key in ('completion_time', 'creation_time', 'start_time'): # Certain Koji API endpoints omit the *_ts values but have the *_time values, so that's # why the *_time values are used if build_info[time_key]: ts_format = r'%Y-%m-%d %H:%M:%S' if len(build_info[time_key].rsplit('.', 1)) == 2: # If there are microseconds, go ahead and parse that too ts_format += r'.%f' build_params[time_key] = datetime.strptime(build_info[time_key], ts_format) owner = User.create_or_update({ 'username': build_info['owner_name'], 'email': '{0}@redhat.com'.format(build_info['owner_name']) })[0] if force_container_label or self.is_container_build(build_info): if original_nvr: build_params['original_nvr'] = original_nvr try: build = ContainerKojiBuild.create_or_update(build_params)[0] except neomodel.exceptions.ConstraintValidationFailed: # This must have errantly been created as a KojiBuild instead of a # ContainerKojiBuild, so let's fix that. build = KojiBuild.nodes.get_or_none(id_=build_params['id_']) if not build: # If there was a constraint validation failure and the build isn't just the # wrong label, then we can't recover. raise build.add_label(ContainerKojiBuild.__label__) build = ContainerKojiBuild.create_or_update(build_params)[0] elif self.is_module_build(build_info): module_extra_info = build_info['extra'].get('typeinfo', {}).get('module') build_params['context'] = module_extra_info.get('context') build_params['mbs_id'] = module_extra_info.get('module_build_service_id') build_params['module_name'] = module_extra_info.get('name') build_params['module_stream'] = module_extra_info.get('stream') build_params['module_version'] = module_extra_info.get('version') try: build = ModuleKojiBuild.create_or_update(build_params)[0] except neomodel.exceptions.ConstraintValidationFailed: # This must have errantly been created as a KojiBuild instead of a # ModuleKojiBuild, so let's fix that. build = KojiBuild.nodes.get_or_none(id_=build_params['id_']) if not build: # If there was a constraint validation failure and the build isn't just the # wrong label, then we can't recover. raise build.add_label(ModuleKojiBuild.__label__) build = ModuleKojiBuild.create_or_update(build_params)[0] else: build = KojiBuild.create_or_update(build_params)[0] build.conditional_connect(build.owner, owner) return build