##// END OF EJS Templates
events: add logging for all events triggered
dan -
r428:8b8343c0 default
parent child Browse files
Show More
@@ -1,66 +1,70 b''
1 1 # Copyright (C) 2016-2016 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 import logging
19 20 from pyramid.threadlocal import get_current_registry
20 21
22 log = logging.getLogger()
23
21 24
22 25 def trigger(event, registry=None):
23 26 """
24 27 Helper method to send an event. This wraps the pyramid logic to send an
25 28 event.
26 29 """
27 30 # For the first step we are using pyramids thread locals here. If the
28 31 # event mechanism works out as a good solution we should think about
29 32 # passing the registry as an argument to get rid of it.
30 33 registry = registry or get_current_registry()
31 34 registry.notify(event)
35 log.debug('event %s triggered', event)
32 36
33 37 # Until we can work around the problem that VCS operations do not have a
34 38 # pyramid context to work with, we send the events to integrations directly
35 39
36 40 # Later it will be possible to use regular pyramid subscribers ie:
37 41 # config.add_subscriber(integrations_event_handler, RhodecodeEvent)
38 42 from rhodecode.integrations import integrations_event_handler
39 43 if isinstance(event, RhodecodeEvent):
40 44 integrations_event_handler(event)
41 45
42 46
43 47 from rhodecode.events.base import RhodecodeEvent
44 48
45 49 from rhodecode.events.user import (
46 50 UserPreCreate,
47 51 UserPreUpdate,
48 52 UserRegistered
49 53 )
50 54
51 55 from rhodecode.events.repo import (
52 56 RepoEvent,
53 57 RepoPreCreateEvent, RepoCreateEvent,
54 58 RepoPreDeleteEvent, RepoDeleteEvent,
55 59 RepoPrePushEvent, RepoPushEvent,
56 60 RepoPrePullEvent, RepoPullEvent,
57 61 )
58 62
59 63 from rhodecode.events.pullrequest import (
60 64 PullRequestEvent,
61 65 PullRequestCreateEvent,
62 66 PullRequestUpdateEvent,
63 67 PullRequestReviewEvent,
64 68 PullRequestMergeEvent,
65 69 PullRequestCloseEvent,
66 70 )
@@ -1,202 +1,201 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 from __future__ import unicode_literals
22 22
23 23 import re
24 24 import logging
25 25 import requests
26 26 import colander
27 27 from celery.task import task
28 28 from mako.template import Template
29 29
30 30 from rhodecode import events
31 31 from rhodecode.translation import lazy_ugettext
32 32 from rhodecode.lib import helpers as h
33 33 from rhodecode.lib.celerylib import run_task
34 34 from rhodecode.lib.colander_utils import strip_whitespace
35 35 from rhodecode.integrations.types.base import IntegrationTypeBase
36 36 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
37 37
38 38 log = logging.getLogger()
39 39
40 40
41 41 class SlackSettingsSchema(IntegrationSettingsSchemaBase):
42 42 service = colander.SchemaNode(
43 43 colander.String(),
44 44 title=lazy_ugettext('Slack service URL'),
45 45 description=h.literal(lazy_ugettext(
46 46 'This can be setup at the '
47 47 '<a href="https://my.slack.com/services/new/incoming-webhook/">'
48 48 'slack app manager</a>')),
49 49 default='',
50 50 placeholder='https://hooks.slack.com/services/...',
51 51 preparer=strip_whitespace,
52 52 validator=colander.url,
53 53 widget='string'
54 54 )
55 55 username = colander.SchemaNode(
56 56 colander.String(),
57 57 title=lazy_ugettext('Username'),
58 58 description=lazy_ugettext('Username to show notifications coming from.'),
59 59 missing='Rhodecode',
60 60 preparer=strip_whitespace,
61 61 widget='string',
62 62 placeholder='Rhodecode'
63 63 )
64 64 channel = colander.SchemaNode(
65 65 colander.String(),
66 66 title=lazy_ugettext('Channel'),
67 67 description=lazy_ugettext('Channel to send notifications to.'),
68 68 missing='',
69 69 preparer=strip_whitespace,
70 70 widget='string',
71 71 placeholder='#general'
72 72 )
73 73 icon_emoji = colander.SchemaNode(
74 74 colander.String(),
75 75 title=lazy_ugettext('Emoji'),
76 76 description=lazy_ugettext('Emoji to use eg. :studio_microphone:'),
77 77 missing='',
78 78 preparer=strip_whitespace,
79 79 widget='string',
80 80 placeholder=':studio_microphone:'
81 81 )
82 82
83 83
84 84 repo_push_template = Template(r'''
85 85 *${data['actor']['username']}* pushed to \
86 86 %if data['push']['branches']:
87 87 ${len(data['push']['branches']) > 1 and 'branches' or 'branch'} \
88 88 ${', '.join('<%s|%s>' % (branch['url'], branch['name']) for branch in data['push']['branches'])} \
89 89 %else:
90 90 unknown branch \
91 91 %endif
92 92 in <${data['repo']['url']}|${data['repo']['repo_name']}>
93 93 >>>
94 94 %for commit in data['push']['commits']:
95 95 <${commit['url']}|${commit['short_id']}> - ${commit['message_html']|html_to_slack_links}
96 96 %endfor
97 97 ''')
98 98
99 99
100 100 class SlackIntegrationType(IntegrationTypeBase):
101 101 key = 'slack'
102 102 display_name = lazy_ugettext('Slack')
103 103 SettingsSchema = SlackSettingsSchema
104 104 valid_events = [
105 105 events.PullRequestCloseEvent,
106 106 events.PullRequestMergeEvent,
107 107 events.PullRequestUpdateEvent,
108 108 events.PullRequestReviewEvent,
109 109 events.PullRequestCreateEvent,
110 110 events.RepoPushEvent,
111 111 events.RepoCreateEvent,
112 112 ]
113 113
114 114 def send_event(self, event):
115 115 if event.__class__ not in self.valid_events:
116 116 log.debug('event not valid: %r' % event)
117 117 return
118 118
119 119 if event.name not in self.settings['events']:
120 120 log.debug('event ignored: %r' % event)
121 121 return
122 122
123 123 data = event.as_dict()
124 124
125 125 text = '*%s* caused a *%s* event' % (
126 126 data['actor']['username'], event.name)
127 127
128 128 log.debug('handling slack event for %s' % event.name)
129 129
130 130 if isinstance(event, events.PullRequestEvent):
131 131 text = self.format_pull_request_event(event, data)
132 132 elif isinstance(event, events.RepoPushEvent):
133 133 text = self.format_repo_push_event(data)
134 134 elif isinstance(event, events.RepoCreateEvent):
135 135 text = self.format_repo_create_event(data)
136 136 else:
137 137 log.error('unhandled event type: %r' % event)
138 138
139 139 run_task(post_text_to_slack, self.settings, text)
140 140
141 141 @classmethod
142 142 def settings_schema(cls):
143 143 schema = SlackSettingsSchema()
144 144 schema.add(colander.SchemaNode(
145 145 colander.Set(),
146 146 widget='checkbox_list',
147 147 choices=sorted([e.name for e in cls.valid_events]),
148 148 description="Events activated for this integration",
149 # default=[e.name for e in cls.valid_events],
150 149 name='events'
151 150 ))
152 151 return schema
153 152
154 153 def format_pull_request_event(self, event, data):
155 154 action = {
156 155 events.PullRequestCloseEvent: 'closed',
157 156 events.PullRequestMergeEvent: 'merged',
158 157 events.PullRequestUpdateEvent: 'updated',
159 158 events.PullRequestReviewEvent: 'reviewed',
160 159 events.PullRequestCreateEvent: 'created',
161 160 }.get(event.__class__, '<unknown action>')
162 161
163 162 return ('Pull request <{url}|#{number}> ({title}) '
164 163 '{action} by {user}').format(
165 164 user=data['actor']['username'],
166 165 number=data['pullrequest']['pull_request_id'],
167 166 url=data['pullrequest']['url'],
168 167 title=data['pullrequest']['title'],
169 168 action=action
170 169 )
171 170
172 171 def format_repo_push_event(self, data):
173 172 result = repo_push_template.render(
174 173 data=data,
175 174 html_to_slack_links=html_to_slack_links,
176 175 )
177 176 return result
178 177
179 178 def format_repo_create_event(self, data):
180 179 return '<{}|{}> ({}) repository created by *{}*'.format(
181 180 data['repo']['url'],
182 181 data['repo']['repo_name'],
183 182 data['repo']['repo_type'],
184 183 data['actor']['username'],
185 184 )
186 185
187 186
188 187 def html_to_slack_links(message):
189 188 return re.compile(r'<a .*?href=["\'](.+?)".*?>(.+?)</a>').sub(
190 189 r'<\1|\2>', message)
191 190
192 191
193 192 @task(ignore_result=True)
194 193 def post_text_to_slack(settings, text):
195 194 log.debug('sending %s to slack %s' % (text, settings['service']))
196 195 resp = requests.post(settings['service'], json={
197 196 "channel": settings.get('channel', ''),
198 197 "username": settings.get('username', 'Rhodecode'),
199 198 "text": text,
200 199 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:')
201 200 })
202 201 resp.raise_for_status() # raise exception on a failed request
General Comments 0
You need to be logged in to leave comments. Login now