# HG changeset patch # User Daniel Dourvaris # Date 2016-07-13 16:12:35 # Node ID 3db70ed62d80b56a73bb84ef8316049321c1bb03 # Parent c2778156ed6bca6aca33f1448dd74dc40bda66aa integrations: add webhook integration diff --git a/rhodecode/integrations/__init__.py b/rhodecode/integrations/__init__.py --- a/rhodecode/integrations/__init__.py +++ b/rhodecode/integrations/__init__.py @@ -20,7 +20,7 @@ import logging from rhodecode.integrations.registry import IntegrationTypeRegistry -from rhodecode.integrations.types import slack +from rhodecode.integrations.types import webhook, slack log = logging.getLogger(__name__) @@ -29,7 +29,12 @@ log = logging.getLogger(__name__) # VCS's not having a pyramid context - move it to pyramid app configuration # includeme level later to allow per instance integration setup integration_type_registry = IntegrationTypeRegistry() -integration_type_registry.register_integration_type(slack.SlackIntegrationType) + +integration_type_registry.register_integration_type( + webhook.WebhookIntegrationType) +integration_type_registry.register_integration_type( + slack.SlackIntegrationType) + def integrations_event_handler(event): """ diff --git a/rhodecode/integrations/types/webhook.py b/rhodecode/integrations/types/webhook.py new file mode 100644 --- /dev/null +++ b/rhodecode/integrations/types/webhook.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2012-2016 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +from __future__ import unicode_literals + +import logging +import requests +import colander +from celery.task import task +from mako.template import Template + +from rhodecode import events +from rhodecode.translation import lazy_ugettext +from rhodecode.integrations.types.base import IntegrationTypeBase +from rhodecode.integrations.schema import IntegrationSettingsSchemaBase + +log = logging.getLogger() + + +class WebhookSettingsSchema(IntegrationSettingsSchemaBase): + url = colander.SchemaNode( + colander.String(), + title=lazy_ugettext('Webhook URL'), + description=lazy_ugettext('URL of the webhook to receive POST event.'), + default='', + validator=colander.url, + placeholder='https://www.example.com/webhook', + widget='string' + ) + secret_token = colander.SchemaNode( + colander.String(), + title=lazy_ugettext('Secret Token'), + description=lazy_ugettext('String used to validate received payloads.'), + default='', + placeholder='secret_token', + widget='string' + ) + + +class WebhookIntegrationType(IntegrationTypeBase): + key = 'webhook' + display_name = lazy_ugettext('Webhook') + valid_events = [ + events.PullRequestCloseEvent, + events.PullRequestMergeEvent, + events.PullRequestUpdateEvent, + events.PullRequestCommentEvent, + events.PullRequestReviewEvent, + events.PullRequestCreateEvent, + events.RepoPushEvent, + events.RepoCreateEvent, + ] + + @classmethod + def settings_schema(cls): + schema = WebhookSettingsSchema() + schema.add(colander.SchemaNode( + colander.Set(), + widget='checkbox_list', + choices=sorted([e.name for e in cls.valid_events]), + description="Events activated for this integration", + name='events' + )) + return schema + + def send_event(self, event): + log.debug('handling event %s with webhook integration %s', + event.name, self) + + if event.__class__ not in self.valid_events: + log.debug('event not valid: %r' % event) + return + + if event.name not in self.settings['events']: + log.debug('event ignored: %r' % event) + return + + data = event.as_dict() + post_to_webhook(data, self.settings) + + +@task(ignore_result=True) +def post_to_webhook(data, settings): + log.debug('sending event:%s to webhook %s', data['name'], settings['url']) + resp = requests.post(settings['url'], json={ + 'token': settings['secret_token'], + 'event': data + }) + resp.raise_for_status() # raise exception on a failed request