From 9b06951e6693a9541dad03b61ab87b32da9b8f97 Mon Sep 17 00:00:00 2001 From: Jan Kaluza Date: Nov 14 2017 07:08:57 +0000 Subject: Allow manual trigger of container images rebuild. --- diff --git a/conf/config.py b/conf/config.py index e950f36..2e48831 100644 --- a/conf/config.py +++ b/conf/config.py @@ -21,7 +21,7 @@ class BaseConfiguration(object): HOST = '0.0.0.0' PORT = 5001 - SERVER_NAME = 'localhost' + SERVER_NAME = 'localhost:5001' DEBUG = False # Global network-related values, in seconds @@ -46,6 +46,7 @@ class BaseConfiguration(object): # Parsers defined for parse specific messages PARSERS = [ + 'freshmaker.parsers.internal:FreshmakerManualRebuildParser', 'freshmaker.parsers.bodhi:BodhiUpdateCompleteStableParser', 'freshmaker.parsers.git:GitReceiveParser', 'freshmaker.parsers.koji:KojiTaskStateChangeParser', @@ -54,6 +55,7 @@ class BaseConfiguration(object): # List of enabled composing handlers. HANDLERS = [ + "freshmaker.handlers.internal:FreshmakerManualRebuildHandler", "freshmaker.handlers.bodhi:BodhiUpdateCompleteStableHandler", "freshmaker.handlers.git:GitDockerfileChangeHandler", "freshmaker.handlers.git:GitModuleMetadataChangeHandler", @@ -243,6 +245,7 @@ class TestConfiguration(BaseConfiguration): os.path.join(dbdir, 'tests', 'test_freshmaker.db')) MESSAGING = 'in_memory' + MESSAGING_SENDER = 'in_memory' PDC_URL = 'http://pdc.fedoraproject.org/rest_api/v1' # Global network-related values, in seconds diff --git a/conf/configrh.py b/conf/configrh.py index 82980ba..015a35d 100644 --- a/conf/configrh.py +++ b/conf/configrh.py @@ -56,6 +56,11 @@ class BaseConfiguration(config.BaseConfiguration): PULP_USERNAME = '' PULP_PASSWORD = '' + AUTH_BACKEND = 'kerberos' + # Replace with real ldap server URL + AUTH_LDAP_SERVER = '' + AUTH_LDAP_GROUP_BASE = 'ou=groups,dc=redhat,dc=com' + class DevConfiguration(BaseConfiguration): DEBUG = True diff --git a/freshmaker/__init__.py b/freshmaker/__init__.py index 80882e6..d56746a 100644 --- a/freshmaker/__init__.py +++ b/freshmaker/__init__.py @@ -43,10 +43,10 @@ db = SQLAlchemy(app) init_logging(conf) log = getLogger(__name__) -from freshmaker import views # noqa - login_manager = LoginManager() login_manager.init_app(app) from freshmaker.auth import init_auth # noqa init_auth(login_manager, conf.auth_backend) + +from freshmaker import views # noqa diff --git a/freshmaker/auth.py b/freshmaker/auth.py index 3f6e44c..d3fda62 100644 --- a/freshmaker/auth.py +++ b/freshmaker/auth.py @@ -107,7 +107,7 @@ def load_openidc_user(request): token = request.environ.get('OIDC_access_token') if not token: - raise Unauthorized('Missing token passed to ODCS.') + raise Unauthorized('Missing token passed to Freshmaker.') scope = request.environ.get('OIDC_CLAIM_scope') if not scope: @@ -184,7 +184,7 @@ def init_auth(login_manager, backend): if backend == 'noauth': # Do not enable any authentication backend working with frontend # authentication module in Apache. - log.warn("Authorization is disabled in ODCS configuration.") + log.warn("Authorization is disabled in Freshmaker configuration.") return if backend == 'kerberos': _validate_kerberos_config() diff --git a/freshmaker/config.py b/freshmaker/config.py index 5403672..0c93b85 100644 --- a/freshmaker/config.py +++ b/freshmaker/config.py @@ -127,6 +127,10 @@ class Config(object): 'type': str, 'default': 'fedmsg', 'desc': 'The messaging system to use.'}, + 'messaging_sender': { + 'type': str, + 'default': 'fedmsg', + 'desc': 'The messaging system to use for sending msgs.'}, 'messaging_topic_prefix': { 'type': list, 'default': ['org.fedoraproject.prod'], @@ -365,3 +369,9 @@ class Config(object): if s not in ("fedmsg", "amq", "in_memory", "rhmsg"): raise ValueError("Unsupported messaging system.") self._messaging = s + + def _setifok_messaging_sender(self, s): + s = str(s) + if s not in ("fedmsg", "amq", "in_memory", "rhmsg"): + raise ValueError("Unsupported messaging system.") + self._messaging_sender = s diff --git a/freshmaker/errata.py b/freshmaker/errata.py index 940b002..1f14fd4 100644 --- a/freshmaker/errata.py +++ b/freshmaker/errata.py @@ -25,7 +25,9 @@ import requests import dogpile.cache from requests_kerberos import HTTPKerberosAuth -from freshmaker.events import BrewSignRPMEvent, ErrataAdvisoryStateChangedEvent +from freshmaker.events import ( + BrewSignRPMEvent, ErrataAdvisoryStateChangedEvent, + FreshmakerManualRebuildEvent) from freshmaker import conf, log @@ -61,14 +63,17 @@ class Errata(object): product_region = dogpile.cache.make_region().configure( conf.dogpile_cache_backend, expiration_time=24 * 3600) - def __init__(self, server_url): + def __init__(self, server_url=None): """ Initializes the Errata instance. :param str server_url: Base URL of Errata server. """ self._rest_api_ver = 'api/v1' - self.server_url = server_url.rstrip('/') + if server_url is not None: + self.server_url = server_url.rstrip('/') + else: + self.server_url = conf.errata_tool_server_url.rstrip('/') def _errata_rest_get(self, endpoint): """Request REST-style API @@ -92,6 +97,9 @@ class Errata(object): r.raise_for_status() return r.json() + def get_advisory(self, errata_id): + return self._errata_http_get('advisory/{0}.json'.format(errata_id)) + @region.cache_on_arguments() def _advisories_from_nvr(self, nvr): """ @@ -127,9 +135,9 @@ class Errata(object): """ if isinstance(event, BrewSignRPMEvent): return self._advisories_from_nvr(event.nvr) - elif isinstance(event, ErrataAdvisoryStateChangedEvent): - data = self._errata_http_get( - "advisory/%s.json" % str(event.errata_id)) + elif (isinstance(event, ErrataAdvisoryStateChangedEvent) or + isinstance(event, FreshmakerManualRebuildEvent)): + data = self.get_advisory(event.errata_id) advisory = ErrataAdvisory( data["id"], data["advisory_name"], data["status"], data["security_impact"]) diff --git a/freshmaker/events.py b/freshmaker/events.py index ffcd100..ddbc0aa 100644 --- a/freshmaker/events.py +++ b/freshmaker/events.py @@ -35,12 +35,15 @@ class BaseEvent(object): _parsers = {} - def __init__(self, msg_id): + def __init__(self, msg_id, manual=False): """ A base class to abstract events from different fedmsg messages. :param msg_id: the id of the msg (e.g. 2016-SomeGUID) + :param manual: True if the event was trigerred manually by Freshmaker + REST API. """ self.msg_id = msg_id + self.manual = manual # Moksha calls `consumer.validate` on messages that it receives, and # even though we have validation turned off in the config there's still @@ -310,5 +313,11 @@ class ODCSComposeStateChangeEvent(BaseEvent): """Represent a compose' state change event from ODCS""" def __init__(self, msg_id, compose): - self.msg_id = msg_id + super(ODCSComposeStateChangeEvent, self).__init__(msg_id) self.compose = compose + + +class FreshmakerManualRebuildEvent(BaseEvent): + def __init__(self, msg_id, errata_id=None): + super(FreshmakerManualRebuildEvent, self).__init__(msg_id) + self.errata_id = errata_id diff --git a/freshmaker/handlers/brew/sign_rpm.py b/freshmaker/handlers/brew/sign_rpm.py index 8b192a7..9fc7f7a 100644 --- a/freshmaker/handlers/brew/sign_rpm.py +++ b/freshmaker/handlers/brew/sign_rpm.py @@ -21,7 +21,6 @@ # # Written by Chenxiong Qi -from freshmaker import conf from freshmaker import log from freshmaker import db from freshmaker.events import BrewSignRPMEvent, ErrataAdvisoryRPMsSignedEvent @@ -68,7 +67,7 @@ class BrewSignRPMHandler(BaseHandler): # When get a signed RPM, first step is to find out advisories # containing that RPM and ensure all builds are signed. - errata = Errata(conf.errata_tool_server_url) + errata = Errata() advisories = errata.advisories_from_event(event) # Filter out advisories which are not allowed by configuration. diff --git a/freshmaker/handlers/errata/errata_advisory_rpms_signed.py b/freshmaker/handlers/errata/errata_advisory_rpms_signed.py index d536a47..f755d23 100644 --- a/freshmaker/handlers/errata/errata_advisory_rpms_signed.py +++ b/freshmaker/handlers/errata/errata_advisory_rpms_signed.py @@ -65,18 +65,21 @@ class ErrataAdvisoryRPMsSignedHandler(BaseHandler): advisory. """ + self.event = event + # Check if we are allowed to build this advisory. - if not self.allow_build( + if not event.manual and not self.allow_build( ArtifactType.IMAGE, advisory_name=event.errata_name, advisory_security_impact=event.security_impact): - log.info("Errata advisory %s not allowed to trigger rebuilds.", - event.errata_name) + msg = 'Errata advisory {0} not allowed to trigger ' \ + 'rebuilds.'.format(event.errata_id) + log.info(msg) return [] # Generate the Database representation of `event`. db_event = Event.get_or_create( db.session, event.msg_id, event.search_key, event.__class__, - released=False) + released=False, manual=event.manual) db.session.commit() self.set_context(db_event) @@ -177,7 +180,7 @@ class ErrataAdvisoryRPMsSignedHandler(BaseHandler): errata_id = int(db_event.search_key) packages = [] - errata = Errata(conf.errata_tool_server_url) + errata = Errata() builds = errata.get_builds(errata_id) compose_source = None for nvr in builds: @@ -495,7 +498,7 @@ class ErrataAdvisoryRPMsSignedHandler(BaseHandler): image_name = koji.parse_NVR(image["brew"]["build"])['name'] - if not self.allow_build( + if not self.event.manual and not self.allow_build( ArtifactType.IMAGE, image_name=image_name): log.info("Skipping rebuild of image %s, not allowed by " "configuration", image_name) @@ -512,7 +515,7 @@ class ErrataAdvisoryRPMsSignedHandler(BaseHandler): :param int errata_id: Errata ID. """ - errata = Errata(conf.errata_tool_server_url) + errata = Errata() errata_id = int(errata_id) # Use the errata_id to find out Pulp repository IDs from Errata Tool diff --git a/freshmaker/handlers/errata/errata_advisory_state_changed.py b/freshmaker/handlers/errata/errata_advisory_state_changed.py index cc51a47..a33cf5a 100644 --- a/freshmaker/handlers/errata/errata_advisory_state_changed.py +++ b/freshmaker/handlers/errata/errata_advisory_state_changed.py @@ -19,7 +19,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -from freshmaker import db, conf, log +from freshmaker import db, log from freshmaker.events import ( ErrataAdvisoryStateChangedEvent, ErrataAdvisoryRPMsSignedEvent) from freshmaker.models import Event, EVENT_TYPES @@ -82,7 +82,7 @@ class ErrataAdvisoryStateChangedHandler(BaseHandler): return [] # Get additional info from Errata to fill in the needed data. - errata = Errata(conf.errata_tool_server_url) + errata = Errata() advisories = errata.advisories_from_event(event) if not advisories: log.error("Unknown Errata advisory %d" % errata_id) diff --git a/freshmaker/handlers/internal/__init__.py b/freshmaker/handlers/internal/__init__.py new file mode 100644 index 0000000..4cd6e36 --- /dev/null +++ b/freshmaker/handlers/internal/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from .manual_rebuild import FreshmakerManualRebuildHandler # noqa diff --git a/freshmaker/handlers/internal/manual_rebuild.py b/freshmaker/handlers/internal/manual_rebuild.py new file mode 100644 index 0000000..5b0dd58 --- /dev/null +++ b/freshmaker/handlers/internal/manual_rebuild.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Jan Kaluza + +from freshmaker import db, log +from freshmaker.models import Event, EVENT_TYPES +from freshmaker.handlers import ContainerBuildHandler +from freshmaker.events import ( + FreshmakerManualRebuildEvent, ErrataAdvisoryRPMsSignedEvent) +from freshmaker.errata import Errata + +__all__ = ('FreshmakerManualRebuildHandler',) + + +class FreshmakerManualRebuildHandler(ContainerBuildHandler): + """Start image rebuild with this compose containing included packages""" + + def can_handle(self, event): + if not isinstance(event, FreshmakerManualRebuildEvent): + return False + return True + + def rebuild_advisory_if_not_exists(self, event, errata_id): + """ + Initiates rebuild of artifacts based on Errata advisory with + `errata_id` id. + + :rtype: List of ErrataAdvisoryRPMsSignedEvent instances. + :return: List of extra events generated to initiate the rebuild. + """ + + db_event = db.session.query(Event).filter_by( + event_type_id=EVENT_TYPES[ErrataAdvisoryRPMsSignedEvent], + search_key=str(errata_id)).first() + if db_event: + log.info("Ignoring Errata advisory %d - it already exists in " + "Freshmaker db.", errata_id) + return [] + + # Get additional info from Errata to fill in the needed data. + errata = Errata() + advisories = errata.advisories_from_event(event) + if not advisories: + log.error("Unknown Errata advisory %d" % errata_id) + return [] + + log.info("Generating ErrataAdvisoryRPMsSignedEvent for Errata " + "advisory %d - manually triggered rebuild.", errata_id) + advisory = advisories[0] + new_event = ErrataAdvisoryRPMsSignedEvent( + event.msg_id + "." + str(advisory.name), advisory.name, + advisory.errata_id, advisory.security_impact) + new_event.manual = True + return [new_event] + + def handle(self, event): + extra_events = [] + + if event.errata_id: + extra_events += self.rebuild_advisory_if_not_exists(event, event.errata_id) + + return extra_events diff --git a/freshmaker/messaging.py b/freshmaker/messaging.py index f8f5ab4..8c4b290 100644 --- a/freshmaker/messaging.py +++ b/freshmaker/messaging.py @@ -40,7 +40,7 @@ def publish(topic, msg): :return: the value returned from underlying backend "send" method. """ try: - handler = _messaging_backends[conf.messaging]['publish'] + handler = _messaging_backends[conf.messaging_sender]['publish'] except KeyError: raise KeyError("No messaging backend found for %r" % conf.messaging) return handler(topic, msg) diff --git a/freshmaker/migrations/versions/2f5a2f4385a0_add_manual_triggered_to_event_model.py b/freshmaker/migrations/versions/2f5a2f4385a0_add_manual_triggered_to_event_model.py new file mode 100644 index 0000000..1a3ff15 --- /dev/null +++ b/freshmaker/migrations/versions/2f5a2f4385a0_add_manual_triggered_to_event_model.py @@ -0,0 +1,26 @@ +"""Add manual_triggered to Event model + +Revision ID: 2f5a2f4385a0 +Revises: 2acc88805404 +Create Date: 2017-11-01 14:03:02.555397 + +""" + +# revision identifiers, used by Alembic. +revision = '2f5a2f4385a0' +down_revision = '2acc88805404' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('events', sa.Column('manual_triggered', sa.Boolean())) + ### end Alembic commands ### + + +def downgrade(): + ### commands auto generated by Alembic - please adjust! ### + op.drop_column('events', 'manual_triggered') + ### end Alembic commands ### diff --git a/freshmaker/models.py b/freshmaker/models.py index e8e5a86..dbc9387 100644 --- a/freshmaker/models.py +++ b/freshmaker/models.py @@ -127,8 +127,14 @@ class Event(FreshmakerBase): default=None, doc='Used to include new version packages to rebuild docker images') + manual_triggered = db.Column( + db.Boolean, + default=False, + doc='Whether this event is triggered manually') + @classmethod - def create(cls, session, message_id, search_key, event_type, released=True): + def create(cls, session, message_id, search_key, event_type, + released=True, manual=False): if event_type in EVENT_TYPES: event_type = EVENT_TYPES[event_type] event = cls( @@ -136,6 +142,7 @@ class Event(FreshmakerBase): search_key=search_key, event_type_id=event_type, released=released, + manual_triggered=manual, ) session.add(event) return event @@ -145,11 +152,13 @@ class Event(FreshmakerBase): return session.query(cls).filter_by(message_id=message_id).first() @classmethod - def get_or_create(cls, session, message_id, search_key, event_type, released=True): + def get_or_create(cls, session, message_id, search_key, event_type, + released=True, manual=False): instance = cls.get(session, message_id) if instance: return instance - return cls.create(session, message_id, search_key, event_type, released) + return cls.create(session, message_id, search_key, event_type, + released=released, manual=manual) @classmethod def get_unreleased(cls, session): diff --git a/freshmaker/parsers/internal/__init__.py b/freshmaker/parsers/internal/__init__.py new file mode 100644 index 0000000..9d44c11 --- /dev/null +++ b/freshmaker/parsers/internal/__init__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2016 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from .manual_rebuild import FreshmakerManualRebuildParser # noqa diff --git a/freshmaker/parsers/internal/manual_rebuild.py b/freshmaker/parsers/internal/manual_rebuild.py new file mode 100644 index 0000000..c698fd8 --- /dev/null +++ b/freshmaker/parsers/internal/manual_rebuild.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from freshmaker.parsers import BaseParser +from freshmaker.events import FreshmakerManualRebuildEvent + + +class FreshmakerManualRebuildParser(BaseParser): + """Parser parsing odcs.compose.state.change""" + + name = "FreshmakerManualRebuildEvent" + topic_suffixes = ["freshmaker.manual.rebuild"] + + def can_parse(self, topic, msg): + return any([topic.endswith(s) for s in self.topic_suffixes]) + + def parse(self, topic, msg): + msg_id = msg.get('msg_id') + inner_msg = msg.get('msg') + errata_id = inner_msg.get('errata_id') + + return FreshmakerManualRebuildEvent(msg_id, errata_id=errata_id) diff --git a/freshmaker/views.py b/freshmaker/views.py index 8b33737..e3c83f4 100644 --- a/freshmaker/views.py +++ b/freshmaker/views.py @@ -32,6 +32,8 @@ from freshmaker.api_utils import pagination_metadata from freshmaker.api_utils import filter_artifact_builds from freshmaker.api_utils import filter_events from freshmaker.api_utils import json_error +from freshmaker.auth import login_required, requires_role +from freshmaker import messaging api_v1 = { 'event_types': { @@ -108,6 +110,12 @@ api_v1 = { 'methods': ['GET'], } }, + 'manual_trigger': { + 'url': '/api/1/builds/', + 'options': { + 'methods': ['POST'], + } + }, }, } @@ -214,6 +222,18 @@ class BuildAPI(MethodView): else: return json_error(404, "Not Found", "No such build found.") + @login_required + @requires_role('admins') + def post(self): + """Trigger image rebuild""" + data = request.get_json(force=True) + if 'errata_id' not in data: + return json_error( + 400, 'Bad Request', 'Missing errata_id in request') + + messaging.publish("manual.rebuild", data) + return jsonify(data), 200 + API_V1_MAPPING = { 'events': EventAPI, diff --git a/tests/test_errata_advisory_state_changed.py b/tests/test_errata_advisory_state_changed.py index c63d217..2eb48d6 100644 --- a/tests/test_errata_advisory_state_changed.py +++ b/tests/test_errata_advisory_state_changed.py @@ -204,6 +204,8 @@ class TestAllowBuild(unittest.TestCase): """ handler = ErrataAdvisoryRPMsSignedHandler() + handler.event = ErrataAdvisoryRPMsSignedEvent( + "123", "RHSA-2017", 123, "None") image = {"brew": {"build": "foo-1-2.3"}} ret = handler._filter_out_not_allowed_builds(image) @@ -239,6 +241,8 @@ class TestAllowBuild(unittest.TestCase): """ handler = ErrataAdvisoryRPMsSignedHandler() + handler.event = ErrataAdvisoryRPMsSignedEvent( + "123", "RHSA-2017", 123, "None") image = {"brew": {"build": "foo-1-2.3"}} ret = handler._filter_out_not_allowed_builds(image) diff --git a/tests/test_freshmaker_manual_rebuild_handler.py b/tests/test_freshmaker_manual_rebuild_handler.py new file mode 100644 index 0000000..8570c4d --- /dev/null +++ b/tests/test_freshmaker_manual_rebuild_handler.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2017 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +# Written by Chenxiong Qi + +import unittest + +from mock import patch + +from freshmaker.handlers.internal import FreshmakerManualRebuildHandler +from freshmaker.events import ( + FreshmakerManualRebuildEvent, ErrataAdvisoryRPMsSignedEvent) + +from freshmaker.errata import ErrataAdvisory +from freshmaker import db +from freshmaker.models import Event + + +class TestFreshmakerManualRebuildHandler(unittest.TestCase): + + def setUp(self): + db.session.remove() + db.drop_all() + db.create_all() + db.session.commit() + + def tearDown(self): + db.session.remove() + db.drop_all() + db.session.commit() + + @patch('freshmaker.errata.Errata.advisories_from_event') + def test_rebuild_if_not_exists(self, advisories_from_event): + handler = FreshmakerManualRebuildHandler() + + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", "Critical")] + ev = FreshmakerManualRebuildEvent("msg123", errata_id=123) + ret = handler.handle(ev) + + self.assertEqual(len(ret), 1) + self.assertEqual(ret[0].errata_id, 123) + self.assertEqual(ret[0].security_impact, "Critical") + self.assertEqual(ret[0].errata_name, "RHSA-2017") + + @patch('freshmaker.errata.Errata.advisories_from_event') + def test_rebuild_if_not_exists_already_exists( + self, advisories_from_event): + handler = FreshmakerManualRebuildHandler() + + Event.create( + db.session, "msg124", "123", ErrataAdvisoryRPMsSignedEvent) + db.session.commit() + + advisories_from_event.return_value = [ + ErrataAdvisory(123, "RHSA-2017", "REL_PREP", "Critical")] + ev = FreshmakerManualRebuildEvent("msg123", errata_id=123) + ret = handler.handle(ev) + + self.assertEqual(len(ret), 0) + + @patch('freshmaker.errata.Errata.advisories_from_event') + def test_rebuild_if_not_exists_unknown_errata_id( + self, advisories_from_event): + advisories_from_event.return_value = [] + handler = FreshmakerManualRebuildHandler() + + ev = FreshmakerManualRebuildEvent("msg123", errata_id=123) + ret = handler.handle(ev) + + self.assertEqual(len(ret), 0) diff --git a/tests/test_messaging.py b/tests/test_messaging.py index a1b45ca..ee34b57 100644 --- a/tests/test_messaging.py +++ b/tests/test_messaging.py @@ -52,23 +52,23 @@ class TestSelectMessagingBackend(unittest.TestCase): } with patch.dict('freshmaker.messaging._messaging_backends', mock_messaging_backends): - with patch.object(conf, 'messaging', new='fedmsg'): + with patch.object(conf, 'messaging_sender', new='fedmsg'): publish('images.ready', fake_msg) _fedmsg_publish.assert_called_once_with( 'images.ready', fake_msg) - with patch.object(conf, 'messaging', new='rhmsg'): + with patch.object(conf, 'messaging_sender', new='rhmsg'): publish('images.ready', fake_msg) _rhmsg_publish.assert_called_once_with( 'images.ready', fake_msg) - with patch.object(conf, 'messaging', new='in_memory'): + with patch.object(conf, 'messaging_sender', new='in_memory'): publish('images.ready', fake_msg) _in_memory_publish.assert_called_once_with( 'images.ready', fake_msg) def test_raise_error_if_backend_not_exists(self): - messaging_patcher = patch.object(conf, 'messaging', new='XXXX') + messaging_patcher = patch.object(conf, 'messaging_sender', new='XXXX') six.assertRaisesRegex( self, ValueError, 'Unsupported messaging system', messaging_patcher.start) @@ -77,7 +77,7 @@ class TestSelectMessagingBackend(unittest.TestCase): class TestPublishToFedmsg(unittest.TestCase): """Test publish message to fedmsg using _fedmsg_publish backend""" - @patch.object(conf, 'messaging', new='fedmsg') + @patch.object(conf, 'messaging_sender', new='fedmsg') @patch.object(conf, 'messaging_backends', new={'fedmsg': {'SERVICE': 'freshmaker'}}) @patch('fedmsg.publish') @@ -94,7 +94,7 @@ class TestPublishToFedmsg(unittest.TestCase): class TestPublishToRhmsg(unittest.TestCase): """Test publish message to UMB using _rhmsg_publish backend""" - @patch.object(conf, 'messaging', new='rhmsg') + @patch.object(conf, 'messaging_sender', new='rhmsg') @patch('rhmsg.activemq.producer.AMQProducer') @patch('proton.Message') def test_publish(self, Message, AMQProducer): diff --git a/tests/test_views.py b/tests/test_views.py index 2b95ec7..25aaa71 100644 --- a/tests/test_views.py +++ b/tests/test_views.py @@ -23,6 +23,8 @@ import unittest import json import six +from mock import patch + from freshmaker import app, db, events, models from freshmaker.types import ArtifactType, ArtifactBuildState @@ -252,5 +254,31 @@ class TestViews(unittest.TestCase): self.assertEqual(data['message'], 'No such build state found.') +class TestManualTriggerRebuild(unittest.TestCase): + def setUp(self): + db.session.remove() + db.drop_all() + db.create_all() + db.session.commit() + + self.client = app.test_client() + + def tearDown(self): + + db.session.remove() + db.drop_all() + db.session.commit() + + @patch('freshmaker.messaging.publish') + def test_manual_rebuild(self, publish): + resp = self.client.post('/api/1/builds/', + data=json.dumps({'errata_id': 1}), + content_type='application/json') + data = json.loads(resp.data.decode('utf-8')) + + self.assertEqual(data["errata_id"], 1) + publish.assert_called_once_with('manual.rebuild', {u'errata_id': 1}) + + if __name__ == '__main__': unittest.main()