##// END OF EJS Templates
logging: Use __name__ when requesting a logger
Martin Bornhold -
r504:669c5d55 default
parent child Browse files
Show More
@@ -1,71 +1,71 b''
1 # Copyright (C) 2016-2016 RhodeCode GmbH
1 # Copyright (C) 2016-2016 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import logging
19 import logging
20 from pyramid.threadlocal import get_current_registry
20 from pyramid.threadlocal import get_current_registry
21
21
22 log = logging.getLogger()
22 log = logging.getLogger(__name__)
23
23
24
24
25 def trigger(event, registry=None):
25 def trigger(event, registry=None):
26 """
26 """
27 Helper method to send an event. This wraps the pyramid logic to send an
27 Helper method to send an event. This wraps the pyramid logic to send an
28 event.
28 event.
29 """
29 """
30 # For the first step we are using pyramids thread locals here. If the
30 # For the first step we are using pyramids thread locals here. If the
31 # event mechanism works out as a good solution we should think about
31 # event mechanism works out as a good solution we should think about
32 # passing the registry as an argument to get rid of it.
32 # passing the registry as an argument to get rid of it.
33 registry = registry or get_current_registry()
33 registry = registry or get_current_registry()
34 registry.notify(event)
34 registry.notify(event)
35 log.debug('event %s triggered', event)
35 log.debug('event %s triggered', event)
36
36
37 # Until we can work around the problem that VCS operations do not have a
37 # Until we can work around the problem that VCS operations do not have a
38 # pyramid context to work with, we send the events to integrations directly
38 # pyramid context to work with, we send the events to integrations directly
39
39
40 # Later it will be possible to use regular pyramid subscribers ie:
40 # Later it will be possible to use regular pyramid subscribers ie:
41 # config.add_subscriber(integrations_event_handler, RhodecodeEvent)
41 # config.add_subscriber(integrations_event_handler, RhodecodeEvent)
42 from rhodecode.integrations import integrations_event_handler
42 from rhodecode.integrations import integrations_event_handler
43 if isinstance(event, RhodecodeEvent):
43 if isinstance(event, RhodecodeEvent):
44 integrations_event_handler(event)
44 integrations_event_handler(event)
45
45
46
46
47 from rhodecode.events.base import RhodecodeEvent
47 from rhodecode.events.base import RhodecodeEvent
48
48
49 from rhodecode.events.user import (
49 from rhodecode.events.user import (
50 UserPreCreate,
50 UserPreCreate,
51 UserPreUpdate,
51 UserPreUpdate,
52 UserRegistered
52 UserRegistered
53 )
53 )
54
54
55 from rhodecode.events.repo import (
55 from rhodecode.events.repo import (
56 RepoEvent,
56 RepoEvent,
57 RepoPreCreateEvent, RepoCreateEvent,
57 RepoPreCreateEvent, RepoCreateEvent,
58 RepoPreDeleteEvent, RepoDeleteEvent,
58 RepoPreDeleteEvent, RepoDeleteEvent,
59 RepoPrePushEvent, RepoPushEvent,
59 RepoPrePushEvent, RepoPushEvent,
60 RepoPrePullEvent, RepoPullEvent,
60 RepoPrePullEvent, RepoPullEvent,
61 )
61 )
62
62
63 from rhodecode.events.pullrequest import (
63 from rhodecode.events.pullrequest import (
64 PullRequestEvent,
64 PullRequestEvent,
65 PullRequestCreateEvent,
65 PullRequestCreateEvent,
66 PullRequestUpdateEvent,
66 PullRequestUpdateEvent,
67 PullRequestCommentEvent,
67 PullRequestCommentEvent,
68 PullRequestReviewEvent,
68 PullRequestReviewEvent,
69 PullRequestMergeEvent,
69 PullRequestMergeEvent,
70 PullRequestCloseEvent,
70 PullRequestCloseEvent,
71 )
71 )
@@ -1,220 +1,220 b''
1 # Copyright (C) 2016-2016 RhodeCode GmbH
1 # Copyright (C) 2016-2016 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import logging
19 import logging
20
20
21 from rhodecode.translation import lazy_ugettext
21 from rhodecode.translation import lazy_ugettext
22 from rhodecode.model.db import User, Repository, Session
22 from rhodecode.model.db import User, Repository, Session
23 from rhodecode.events.base import RhodecodeEvent
23 from rhodecode.events.base import RhodecodeEvent
24
24
25 log = logging.getLogger()
25 log = logging.getLogger(__name__)
26
26
27
27
28 class RepoEvent(RhodecodeEvent):
28 class RepoEvent(RhodecodeEvent):
29 """
29 """
30 Base class for events acting on a repository.
30 Base class for events acting on a repository.
31
31
32 :param repo: a :class:`Repository` instance
32 :param repo: a :class:`Repository` instance
33 """
33 """
34
34
35 def __init__(self, repo):
35 def __init__(self, repo):
36 super(RepoEvent, self).__init__()
36 super(RepoEvent, self).__init__()
37 self.repo = repo
37 self.repo = repo
38
38
39 def as_dict(self):
39 def as_dict(self):
40 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.repo import RepoModel
41 data = super(RepoEvent, self).as_dict()
41 data = super(RepoEvent, self).as_dict()
42 data.update({
42 data.update({
43 'repo': {
43 'repo': {
44 'repo_id': self.repo.repo_id,
44 'repo_id': self.repo.repo_id,
45 'repo_name': self.repo.repo_name,
45 'repo_name': self.repo.repo_name,
46 'repo_type': self.repo.repo_type,
46 'repo_type': self.repo.repo_type,
47 'url': RepoModel().get_url(self.repo)
47 'url': RepoModel().get_url(self.repo)
48 }
48 }
49 })
49 })
50 return data
50 return data
51
51
52 def _commits_as_dict(self, commit_ids):
52 def _commits_as_dict(self, commit_ids):
53 """ Helper function to serialize commit_ids """
53 """ Helper function to serialize commit_ids """
54
54
55 from rhodecode.lib.utils2 import extract_mentioned_users
55 from rhodecode.lib.utils2 import extract_mentioned_users
56 from rhodecode.model.db import Repository
56 from rhodecode.model.db import Repository
57 from rhodecode.lib import helpers as h
57 from rhodecode.lib import helpers as h
58 from rhodecode.lib.helpers import process_patterns
58 from rhodecode.lib.helpers import process_patterns
59 from rhodecode.lib.helpers import urlify_commit_message
59 from rhodecode.lib.helpers import urlify_commit_message
60 if not commit_ids:
60 if not commit_ids:
61 return []
61 return []
62 commits = []
62 commits = []
63 reviewers = []
63 reviewers = []
64 vcs_repo = self.repo.scm_instance(cache=False)
64 vcs_repo = self.repo.scm_instance(cache=False)
65 try:
65 try:
66 for commit_id in commit_ids:
66 for commit_id in commit_ids:
67 cs = vcs_repo.get_changeset(commit_id)
67 cs = vcs_repo.get_changeset(commit_id)
68 cs_data = cs.__json__()
68 cs_data = cs.__json__()
69 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
69 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
70 cs_data['reviewers'] = reviewers
70 cs_data['reviewers'] = reviewers
71 cs_data['url'] = h.url('changeset_home',
71 cs_data['url'] = h.url('changeset_home',
72 repo_name=self.repo.repo_name,
72 repo_name=self.repo.repo_name,
73 revision=cs_data['raw_id'],
73 revision=cs_data['raw_id'],
74 qualified=True
74 qualified=True
75 )
75 )
76 urlified_message, issues_data = process_patterns(
76 urlified_message, issues_data = process_patterns(
77 cs_data['message'], self.repo.repo_name)
77 cs_data['message'], self.repo.repo_name)
78 cs_data['issues'] = issues_data
78 cs_data['issues'] = issues_data
79 cs_data['message_html'] = urlify_commit_message(cs_data['message'],
79 cs_data['message_html'] = urlify_commit_message(cs_data['message'],
80 self.repo.repo_name)
80 self.repo.repo_name)
81 commits.append(cs_data)
81 commits.append(cs_data)
82 except Exception as e:
82 except Exception as e:
83 log.exception(e)
83 log.exception(e)
84 # we don't send any commits when crash happens, only full list matters
84 # we don't send any commits when crash happens, only full list matters
85 # we short circuit then.
85 # we short circuit then.
86 return []
86 return []
87 return commits
87 return commits
88
88
89 def _issues_as_dict(self, commits):
89 def _issues_as_dict(self, commits):
90 """ Helper function to serialize issues from commits """
90 """ Helper function to serialize issues from commits """
91 issues = {}
91 issues = {}
92 for commit in commits:
92 for commit in commits:
93 for issue in commit['issues']:
93 for issue in commit['issues']:
94 issues[issue['id']] = issue
94 issues[issue['id']] = issue
95 return issues
95 return issues
96
96
97
97
98 class RepoPreCreateEvent(RepoEvent):
98 class RepoPreCreateEvent(RepoEvent):
99 """
99 """
100 An instance of this class is emitted as an :term:`event` before a repo is
100 An instance of this class is emitted as an :term:`event` before a repo is
101 created.
101 created.
102 """
102 """
103 name = 'repo-pre-create'
103 name = 'repo-pre-create'
104 display_name = lazy_ugettext('repository pre create')
104 display_name = lazy_ugettext('repository pre create')
105
105
106
106
107 class RepoCreateEvent(RepoEvent):
107 class RepoCreateEvent(RepoEvent):
108 """
108 """
109 An instance of this class is emitted as an :term:`event` whenever a repo is
109 An instance of this class is emitted as an :term:`event` whenever a repo is
110 created.
110 created.
111 """
111 """
112 name = 'repo-create'
112 name = 'repo-create'
113 display_name = lazy_ugettext('repository created')
113 display_name = lazy_ugettext('repository created')
114
114
115
115
116 class RepoPreDeleteEvent(RepoEvent):
116 class RepoPreDeleteEvent(RepoEvent):
117 """
117 """
118 An instance of this class is emitted as an :term:`event` whenever a repo is
118 An instance of this class is emitted as an :term:`event` whenever a repo is
119 created.
119 created.
120 """
120 """
121 name = 'repo-pre-delete'
121 name = 'repo-pre-delete'
122 display_name = lazy_ugettext('repository pre delete')
122 display_name = lazy_ugettext('repository pre delete')
123
123
124
124
125 class RepoDeleteEvent(RepoEvent):
125 class RepoDeleteEvent(RepoEvent):
126 """
126 """
127 An instance of this class is emitted as an :term:`event` whenever a repo is
127 An instance of this class is emitted as an :term:`event` whenever a repo is
128 created.
128 created.
129 """
129 """
130 name = 'repo-delete'
130 name = 'repo-delete'
131 display_name = lazy_ugettext('repository deleted')
131 display_name = lazy_ugettext('repository deleted')
132
132
133
133
134 class RepoVCSEvent(RepoEvent):
134 class RepoVCSEvent(RepoEvent):
135 """
135 """
136 Base class for events triggered by the VCS
136 Base class for events triggered by the VCS
137 """
137 """
138 def __init__(self, repo_name, extras):
138 def __init__(self, repo_name, extras):
139 self.repo = Repository.get_by_repo_name(repo_name)
139 self.repo = Repository.get_by_repo_name(repo_name)
140 if not self.repo:
140 if not self.repo:
141 raise Exception('repo by this name %s does not exist' % repo_name)
141 raise Exception('repo by this name %s does not exist' % repo_name)
142 self.extras = extras
142 self.extras = extras
143 super(RepoVCSEvent, self).__init__(self.repo)
143 super(RepoVCSEvent, self).__init__(self.repo)
144
144
145 @property
145 @property
146 def actor(self):
146 def actor(self):
147 if self.extras.get('username'):
147 if self.extras.get('username'):
148 return User.get_by_username(self.extras['username'])
148 return User.get_by_username(self.extras['username'])
149
149
150 @property
150 @property
151 def actor_ip(self):
151 def actor_ip(self):
152 if self.extras.get('ip'):
152 if self.extras.get('ip'):
153 return self.extras['ip']
153 return self.extras['ip']
154
154
155
155
156 class RepoPrePullEvent(RepoVCSEvent):
156 class RepoPrePullEvent(RepoVCSEvent):
157 """
157 """
158 An instance of this class is emitted as an :term:`event` before commits
158 An instance of this class is emitted as an :term:`event` before commits
159 are pulled from a repo.
159 are pulled from a repo.
160 """
160 """
161 name = 'repo-pre-pull'
161 name = 'repo-pre-pull'
162 display_name = lazy_ugettext('repository pre pull')
162 display_name = lazy_ugettext('repository pre pull')
163
163
164
164
165 class RepoPullEvent(RepoVCSEvent):
165 class RepoPullEvent(RepoVCSEvent):
166 """
166 """
167 An instance of this class is emitted as an :term:`event` after commits
167 An instance of this class is emitted as an :term:`event` after commits
168 are pulled from a repo.
168 are pulled from a repo.
169 """
169 """
170 name = 'repo-pull'
170 name = 'repo-pull'
171 display_name = lazy_ugettext('repository pull')
171 display_name = lazy_ugettext('repository pull')
172
172
173
173
174 class RepoPrePushEvent(RepoVCSEvent):
174 class RepoPrePushEvent(RepoVCSEvent):
175 """
175 """
176 An instance of this class is emitted as an :term:`event` before commits
176 An instance of this class is emitted as an :term:`event` before commits
177 are pushed to a repo.
177 are pushed to a repo.
178 """
178 """
179 name = 'repo-pre-push'
179 name = 'repo-pre-push'
180 display_name = lazy_ugettext('repository pre push')
180 display_name = lazy_ugettext('repository pre push')
181
181
182
182
183 class RepoPushEvent(RepoVCSEvent):
183 class RepoPushEvent(RepoVCSEvent):
184 """
184 """
185 An instance of this class is emitted as an :term:`event` after commits
185 An instance of this class is emitted as an :term:`event` after commits
186 are pushed to a repo.
186 are pushed to a repo.
187
187
188 :param extras: (optional) dict of data from proxied VCS actions
188 :param extras: (optional) dict of data from proxied VCS actions
189 """
189 """
190 name = 'repo-push'
190 name = 'repo-push'
191 display_name = lazy_ugettext('repository push')
191 display_name = lazy_ugettext('repository push')
192
192
193 def __init__(self, repo_name, pushed_commit_ids, extras):
193 def __init__(self, repo_name, pushed_commit_ids, extras):
194 super(RepoPushEvent, self).__init__(repo_name, extras)
194 super(RepoPushEvent, self).__init__(repo_name, extras)
195 self.pushed_commit_ids = pushed_commit_ids
195 self.pushed_commit_ids = pushed_commit_ids
196
196
197 def as_dict(self):
197 def as_dict(self):
198 data = super(RepoPushEvent, self).as_dict()
198 data = super(RepoPushEvent, self).as_dict()
199 branch_url = repo_url = data['repo']['url']
199 branch_url = repo_url = data['repo']['url']
200
200
201 commits = self._commits_as_dict(self.pushed_commit_ids)
201 commits = self._commits_as_dict(self.pushed_commit_ids)
202 issues = self._issues_as_dict(commits)
202 issues = self._issues_as_dict(commits)
203
203
204 branches = set(
204 branches = set(
205 commit['branch'] for commit in commits if commit['branch'])
205 commit['branch'] for commit in commits if commit['branch'])
206 branches = [
206 branches = [
207 {
207 {
208 'name': branch,
208 'name': branch,
209 'url': '{}/changelog?branch={}'.format(
209 'url': '{}/changelog?branch={}'.format(
210 data['repo']['url'], branch)
210 data['repo']['url'], branch)
211 }
211 }
212 for branch in branches
212 for branch in branches
213 ]
213 ]
214
214
215 data['push'] = {
215 data['push'] = {
216 'commits': commits,
216 'commits': commits,
217 'issues': issues,
217 'issues': issues,
218 'branches': branches,
218 'branches': branches,
219 }
219 }
220 return data No newline at end of file
220 return data
@@ -1,37 +1,37 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2012-2016 RhodeCode GmbH
2 # Copyright (C) 2012-2016 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 import logging
20 import logging
21
21
22 log = logging.getLogger()
22 log = logging.getLogger(__name__)
23
23
24
24
25 class IntegrationTypeRegistry(dict):
25 class IntegrationTypeRegistry(dict):
26 """
26 """
27 Registry Class to hold IntegrationTypes
27 Registry Class to hold IntegrationTypes
28 """
28 """
29 def register_integration_type(self, IntegrationType):
29 def register_integration_type(self, IntegrationType):
30 key = IntegrationType.key
30 key = IntegrationType.key
31 if key in self:
31 if key in self:
32 log.warning(
32 log.warning(
33 'Overriding existing integration type %s (%s) with %s' % (
33 'Overriding existing integration type %s (%s) with %s' % (
34 self[key], key, IntegrationType))
34 self[key], key, IntegrationType))
35
35
36 self[key] = IntegrationType
36 self[key] = IntegrationType
37
37
@@ -1,246 +1,246 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from __future__ import unicode_literals
21 from __future__ import unicode_literals
22
22
23 import re
23 import re
24 import logging
24 import logging
25 import requests
25 import requests
26 import colander
26 import colander
27 import textwrap
27 import textwrap
28 from celery.task import task
28 from celery.task import task
29 from mako.template import Template
29 from mako.template import Template
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.translation import lazy_ugettext
32 from rhodecode.translation import lazy_ugettext
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.lib.celerylib import run_task
34 from rhodecode.lib.celerylib import run_task
35 from rhodecode.lib.colander_utils import strip_whitespace
35 from rhodecode.lib.colander_utils import strip_whitespace
36 from rhodecode.integrations.types.base import IntegrationTypeBase
36 from rhodecode.integrations.types.base import IntegrationTypeBase
37 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
37 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
38
38
39 log = logging.getLogger()
39 log = logging.getLogger(__name__)
40
40
41
41
42 class SlackSettingsSchema(IntegrationSettingsSchemaBase):
42 class SlackSettingsSchema(IntegrationSettingsSchemaBase):
43 service = colander.SchemaNode(
43 service = colander.SchemaNode(
44 colander.String(),
44 colander.String(),
45 title=lazy_ugettext('Slack service URL'),
45 title=lazy_ugettext('Slack service URL'),
46 description=h.literal(lazy_ugettext(
46 description=h.literal(lazy_ugettext(
47 'This can be setup at the '
47 'This can be setup at the '
48 '<a href="https://my.slack.com/services/new/incoming-webhook/">'
48 '<a href="https://my.slack.com/services/new/incoming-webhook/">'
49 'slack app manager</a>')),
49 'slack app manager</a>')),
50 default='',
50 default='',
51 placeholder='https://hooks.slack.com/services/...',
51 placeholder='https://hooks.slack.com/services/...',
52 preparer=strip_whitespace,
52 preparer=strip_whitespace,
53 validator=colander.url,
53 validator=colander.url,
54 widget='string'
54 widget='string'
55 )
55 )
56 username = colander.SchemaNode(
56 username = colander.SchemaNode(
57 colander.String(),
57 colander.String(),
58 title=lazy_ugettext('Username'),
58 title=lazy_ugettext('Username'),
59 description=lazy_ugettext('Username to show notifications coming from.'),
59 description=lazy_ugettext('Username to show notifications coming from.'),
60 missing='Rhodecode',
60 missing='Rhodecode',
61 preparer=strip_whitespace,
61 preparer=strip_whitespace,
62 widget='string',
62 widget='string',
63 placeholder='Rhodecode'
63 placeholder='Rhodecode'
64 )
64 )
65 channel = colander.SchemaNode(
65 channel = colander.SchemaNode(
66 colander.String(),
66 colander.String(),
67 title=lazy_ugettext('Channel'),
67 title=lazy_ugettext('Channel'),
68 description=lazy_ugettext('Channel to send notifications to.'),
68 description=lazy_ugettext('Channel to send notifications to.'),
69 missing='',
69 missing='',
70 preparer=strip_whitespace,
70 preparer=strip_whitespace,
71 widget='string',
71 widget='string',
72 placeholder='#general'
72 placeholder='#general'
73 )
73 )
74 icon_emoji = colander.SchemaNode(
74 icon_emoji = colander.SchemaNode(
75 colander.String(),
75 colander.String(),
76 title=lazy_ugettext('Emoji'),
76 title=lazy_ugettext('Emoji'),
77 description=lazy_ugettext('Emoji to use eg. :studio_microphone:'),
77 description=lazy_ugettext('Emoji to use eg. :studio_microphone:'),
78 missing='',
78 missing='',
79 preparer=strip_whitespace,
79 preparer=strip_whitespace,
80 widget='string',
80 widget='string',
81 placeholder=':studio_microphone:'
81 placeholder=':studio_microphone:'
82 )
82 )
83
83
84
84
85 repo_push_template = Template(r'''
85 repo_push_template = Template(r'''
86 *${data['actor']['username']}* pushed to \
86 *${data['actor']['username']}* pushed to \
87 %if data['push']['branches']:
87 %if data['push']['branches']:
88 ${len(data['push']['branches']) > 1 and 'branches' or 'branch'} \
88 ${len(data['push']['branches']) > 1 and 'branches' or 'branch'} \
89 ${', '.join('<%s|%s>' % (branch['url'], branch['name']) for branch in data['push']['branches'])} \
89 ${', '.join('<%s|%s>' % (branch['url'], branch['name']) for branch in data['push']['branches'])} \
90 %else:
90 %else:
91 unknown branch \
91 unknown branch \
92 %endif
92 %endif
93 in <${data['repo']['url']}|${data['repo']['repo_name']}>
93 in <${data['repo']['url']}|${data['repo']['repo_name']}>
94 >>>
94 >>>
95 %for commit in data['push']['commits']:
95 %for commit in data['push']['commits']:
96 <${commit['url']}|${commit['short_id']}> - ${commit['message_html']|html_to_slack_links}
96 <${commit['url']}|${commit['short_id']}> - ${commit['message_html']|html_to_slack_links}
97 %endfor
97 %endfor
98 ''')
98 ''')
99
99
100
100
101 class SlackIntegrationType(IntegrationTypeBase):
101 class SlackIntegrationType(IntegrationTypeBase):
102 key = 'slack'
102 key = 'slack'
103 display_name = lazy_ugettext('Slack')
103 display_name = lazy_ugettext('Slack')
104 SettingsSchema = SlackSettingsSchema
104 SettingsSchema = SlackSettingsSchema
105 valid_events = [
105 valid_events = [
106 events.PullRequestCloseEvent,
106 events.PullRequestCloseEvent,
107 events.PullRequestMergeEvent,
107 events.PullRequestMergeEvent,
108 events.PullRequestUpdateEvent,
108 events.PullRequestUpdateEvent,
109 events.PullRequestCommentEvent,
109 events.PullRequestCommentEvent,
110 events.PullRequestReviewEvent,
110 events.PullRequestReviewEvent,
111 events.PullRequestCreateEvent,
111 events.PullRequestCreateEvent,
112 events.RepoPushEvent,
112 events.RepoPushEvent,
113 events.RepoCreateEvent,
113 events.RepoCreateEvent,
114 ]
114 ]
115
115
116 def send_event(self, event):
116 def send_event(self, event):
117 if event.__class__ not in self.valid_events:
117 if event.__class__ not in self.valid_events:
118 log.debug('event not valid: %r' % event)
118 log.debug('event not valid: %r' % event)
119 return
119 return
120
120
121 if event.name not in self.settings['events']:
121 if event.name not in self.settings['events']:
122 log.debug('event ignored: %r' % event)
122 log.debug('event ignored: %r' % event)
123 return
123 return
124
124
125 data = event.as_dict()
125 data = event.as_dict()
126
126
127 text = '*%s* caused a *%s* event' % (
127 text = '*%s* caused a *%s* event' % (
128 data['actor']['username'], event.name)
128 data['actor']['username'], event.name)
129
129
130 log.debug('handling slack event for %s' % event.name)
130 log.debug('handling slack event for %s' % event.name)
131
131
132 if isinstance(event, events.PullRequestCommentEvent):
132 if isinstance(event, events.PullRequestCommentEvent):
133 text = self.format_pull_request_comment_event(event, data)
133 text = self.format_pull_request_comment_event(event, data)
134 elif isinstance(event, events.PullRequestReviewEvent):
134 elif isinstance(event, events.PullRequestReviewEvent):
135 text = self.format_pull_request_review_event(event, data)
135 text = self.format_pull_request_review_event(event, data)
136 elif isinstance(event, events.PullRequestEvent):
136 elif isinstance(event, events.PullRequestEvent):
137 text = self.format_pull_request_event(event, data)
137 text = self.format_pull_request_event(event, data)
138 elif isinstance(event, events.RepoPushEvent):
138 elif isinstance(event, events.RepoPushEvent):
139 text = self.format_repo_push_event(data)
139 text = self.format_repo_push_event(data)
140 elif isinstance(event, events.RepoCreateEvent):
140 elif isinstance(event, events.RepoCreateEvent):
141 text = self.format_repo_create_event(data)
141 text = self.format_repo_create_event(data)
142 else:
142 else:
143 log.error('unhandled event type: %r' % event)
143 log.error('unhandled event type: %r' % event)
144
144
145 run_task(post_text_to_slack, self.settings, text)
145 run_task(post_text_to_slack, self.settings, text)
146
146
147 @classmethod
147 @classmethod
148 def settings_schema(cls):
148 def settings_schema(cls):
149 schema = SlackSettingsSchema()
149 schema = SlackSettingsSchema()
150 schema.add(colander.SchemaNode(
150 schema.add(colander.SchemaNode(
151 colander.Set(),
151 colander.Set(),
152 widget='checkbox_list',
152 widget='checkbox_list',
153 choices=sorted([e.name for e in cls.valid_events]),
153 choices=sorted([e.name for e in cls.valid_events]),
154 description="Events activated for this integration",
154 description="Events activated for this integration",
155 name='events'
155 name='events'
156 ))
156 ))
157 return schema
157 return schema
158
158
159 def format_pull_request_comment_event(self, event, data):
159 def format_pull_request_comment_event(self, event, data):
160 comment_text = data['comment']['text']
160 comment_text = data['comment']['text']
161 if len(comment_text) > 200:
161 if len(comment_text) > 200:
162 comment_text = '<{comment_url}|{comment_text}...>'.format(
162 comment_text = '<{comment_url}|{comment_text}...>'.format(
163 comment_text=comment_text[:200],
163 comment_text=comment_text[:200],
164 comment_url=data['comment']['url'],
164 comment_url=data['comment']['url'],
165 )
165 )
166
166
167 comment_status = ''
167 comment_status = ''
168 if data['comment']['status']:
168 if data['comment']['status']:
169 comment_status = '[{}]: '.format(data['comment']['status'])
169 comment_status = '[{}]: '.format(data['comment']['status'])
170
170
171 return (textwrap.dedent(
171 return (textwrap.dedent(
172 '''
172 '''
173 {user} commented on pull request <{pr_url}|#{number}> - {pr_title}:
173 {user} commented on pull request <{pr_url}|#{number}> - {pr_title}:
174 >>> {comment_status}{comment_text}
174 >>> {comment_status}{comment_text}
175 ''').format(
175 ''').format(
176 comment_status=comment_status,
176 comment_status=comment_status,
177 user=data['actor']['username'],
177 user=data['actor']['username'],
178 number=data['pullrequest']['pull_request_id'],
178 number=data['pullrequest']['pull_request_id'],
179 pr_url=data['pullrequest']['url'],
179 pr_url=data['pullrequest']['url'],
180 pr_status=data['pullrequest']['status'],
180 pr_status=data['pullrequest']['status'],
181 pr_title=data['pullrequest']['title'],
181 pr_title=data['pullrequest']['title'],
182 comment_text=comment_text
182 comment_text=comment_text
183 )
183 )
184 )
184 )
185
185
186 def format_pull_request_review_event(self, event, data):
186 def format_pull_request_review_event(self, event, data):
187 return (textwrap.dedent(
187 return (textwrap.dedent(
188 '''
188 '''
189 Status changed to {pr_status} for pull request <{pr_url}|#{number}> - {pr_title}
189 Status changed to {pr_status} for pull request <{pr_url}|#{number}> - {pr_title}
190 ''').format(
190 ''').format(
191 user=data['actor']['username'],
191 user=data['actor']['username'],
192 number=data['pullrequest']['pull_request_id'],
192 number=data['pullrequest']['pull_request_id'],
193 pr_url=data['pullrequest']['url'],
193 pr_url=data['pullrequest']['url'],
194 pr_status=data['pullrequest']['status'],
194 pr_status=data['pullrequest']['status'],
195 pr_title=data['pullrequest']['title'],
195 pr_title=data['pullrequest']['title'],
196 )
196 )
197 )
197 )
198
198
199 def format_pull_request_event(self, event, data):
199 def format_pull_request_event(self, event, data):
200 action = {
200 action = {
201 events.PullRequestCloseEvent: 'closed',
201 events.PullRequestCloseEvent: 'closed',
202 events.PullRequestMergeEvent: 'merged',
202 events.PullRequestMergeEvent: 'merged',
203 events.PullRequestUpdateEvent: 'updated',
203 events.PullRequestUpdateEvent: 'updated',
204 events.PullRequestCreateEvent: 'created',
204 events.PullRequestCreateEvent: 'created',
205 }.get(event.__class__, str(event.__class__))
205 }.get(event.__class__, str(event.__class__))
206
206
207 return ('Pull request <{url}|#{number}> - {title} '
207 return ('Pull request <{url}|#{number}> - {title} '
208 '{action} by {user}').format(
208 '{action} by {user}').format(
209 user=data['actor']['username'],
209 user=data['actor']['username'],
210 number=data['pullrequest']['pull_request_id'],
210 number=data['pullrequest']['pull_request_id'],
211 url=data['pullrequest']['url'],
211 url=data['pullrequest']['url'],
212 title=data['pullrequest']['title'],
212 title=data['pullrequest']['title'],
213 action=action
213 action=action
214 )
214 )
215
215
216 def format_repo_push_event(self, data):
216 def format_repo_push_event(self, data):
217 result = repo_push_template.render(
217 result = repo_push_template.render(
218 data=data,
218 data=data,
219 html_to_slack_links=html_to_slack_links,
219 html_to_slack_links=html_to_slack_links,
220 )
220 )
221 return result
221 return result
222
222
223 def format_repo_create_event(self, data):
223 def format_repo_create_event(self, data):
224 return '<{}|{}> ({}) repository created by *{}*'.format(
224 return '<{}|{}> ({}) repository created by *{}*'.format(
225 data['repo']['url'],
225 data['repo']['url'],
226 data['repo']['repo_name'],
226 data['repo']['repo_name'],
227 data['repo']['repo_type'],
227 data['repo']['repo_type'],
228 data['actor']['username'],
228 data['actor']['username'],
229 )
229 )
230
230
231
231
232 def html_to_slack_links(message):
232 def html_to_slack_links(message):
233 return re.compile(r'<a .*?href=["\'](.+?)".*?>(.+?)</a>').sub(
233 return re.compile(r'<a .*?href=["\'](.+?)".*?>(.+?)</a>').sub(
234 r'<\1|\2>', message)
234 r'<\1|\2>', message)
235
235
236
236
237 @task(ignore_result=True)
237 @task(ignore_result=True)
238 def post_text_to_slack(settings, text):
238 def post_text_to_slack(settings, text):
239 log.debug('sending %s to slack %s' % (text, settings['service']))
239 log.debug('sending %s to slack %s' % (text, settings['service']))
240 resp = requests.post(settings['service'], json={
240 resp = requests.post(settings['service'], json={
241 "channel": settings.get('channel', ''),
241 "channel": settings.get('channel', ''),
242 "username": settings.get('username', 'Rhodecode'),
242 "username": settings.get('username', 'Rhodecode'),
243 "text": text,
243 "text": text,
244 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:')
244 "icon_emoji": settings.get('icon_emoji', ':studio_microphone:')
245 })
245 })
246 resp.raise_for_status() # raise exception on a failed request
246 resp.raise_for_status() # raise exception on a failed request
@@ -1,106 +1,106 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from __future__ import unicode_literals
21 from __future__ import unicode_literals
22
22
23 import logging
23 import logging
24 import requests
24 import requests
25 import colander
25 import colander
26 from celery.task import task
26 from celery.task import task
27 from mako.template import Template
27 from mako.template import Template
28
28
29 from rhodecode import events
29 from rhodecode import events
30 from rhodecode.translation import lazy_ugettext
30 from rhodecode.translation import lazy_ugettext
31 from rhodecode.integrations.types.base import IntegrationTypeBase
31 from rhodecode.integrations.types.base import IntegrationTypeBase
32 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
32 from rhodecode.integrations.schema import IntegrationSettingsSchemaBase
33
33
34 log = logging.getLogger()
34 log = logging.getLogger(__name__)
35
35
36
36
37 class WebhookSettingsSchema(IntegrationSettingsSchemaBase):
37 class WebhookSettingsSchema(IntegrationSettingsSchemaBase):
38 url = colander.SchemaNode(
38 url = colander.SchemaNode(
39 colander.String(),
39 colander.String(),
40 title=lazy_ugettext('Webhook URL'),
40 title=lazy_ugettext('Webhook URL'),
41 description=lazy_ugettext('URL of the webhook to receive POST event.'),
41 description=lazy_ugettext('URL of the webhook to receive POST event.'),
42 default='',
42 default='',
43 validator=colander.url,
43 validator=colander.url,
44 placeholder='https://www.example.com/webhook',
44 placeholder='https://www.example.com/webhook',
45 widget='string'
45 widget='string'
46 )
46 )
47 secret_token = colander.SchemaNode(
47 secret_token = colander.SchemaNode(
48 colander.String(),
48 colander.String(),
49 title=lazy_ugettext('Secret Token'),
49 title=lazy_ugettext('Secret Token'),
50 description=lazy_ugettext('String used to validate received payloads.'),
50 description=lazy_ugettext('String used to validate received payloads.'),
51 default='',
51 default='',
52 placeholder='secret_token',
52 placeholder='secret_token',
53 widget='string'
53 widget='string'
54 )
54 )
55
55
56
56
57 class WebhookIntegrationType(IntegrationTypeBase):
57 class WebhookIntegrationType(IntegrationTypeBase):
58 key = 'webhook'
58 key = 'webhook'
59 display_name = lazy_ugettext('Webhook')
59 display_name = lazy_ugettext('Webhook')
60 valid_events = [
60 valid_events = [
61 events.PullRequestCloseEvent,
61 events.PullRequestCloseEvent,
62 events.PullRequestMergeEvent,
62 events.PullRequestMergeEvent,
63 events.PullRequestUpdateEvent,
63 events.PullRequestUpdateEvent,
64 events.PullRequestCommentEvent,
64 events.PullRequestCommentEvent,
65 events.PullRequestReviewEvent,
65 events.PullRequestReviewEvent,
66 events.PullRequestCreateEvent,
66 events.PullRequestCreateEvent,
67 events.RepoPushEvent,
67 events.RepoPushEvent,
68 events.RepoCreateEvent,
68 events.RepoCreateEvent,
69 ]
69 ]
70
70
71 @classmethod
71 @classmethod
72 def settings_schema(cls):
72 def settings_schema(cls):
73 schema = WebhookSettingsSchema()
73 schema = WebhookSettingsSchema()
74 schema.add(colander.SchemaNode(
74 schema.add(colander.SchemaNode(
75 colander.Set(),
75 colander.Set(),
76 widget='checkbox_list',
76 widget='checkbox_list',
77 choices=sorted([e.name for e in cls.valid_events]),
77 choices=sorted([e.name for e in cls.valid_events]),
78 description="Events activated for this integration",
78 description="Events activated for this integration",
79 name='events'
79 name='events'
80 ))
80 ))
81 return schema
81 return schema
82
82
83 def send_event(self, event):
83 def send_event(self, event):
84 log.debug('handling event %s with webhook integration %s',
84 log.debug('handling event %s with webhook integration %s',
85 event.name, self)
85 event.name, self)
86
86
87 if event.__class__ not in self.valid_events:
87 if event.__class__ not in self.valid_events:
88 log.debug('event not valid: %r' % event)
88 log.debug('event not valid: %r' % event)
89 return
89 return
90
90
91 if event.name not in self.settings['events']:
91 if event.name not in self.settings['events']:
92 log.debug('event ignored: %r' % event)
92 log.debug('event ignored: %r' % event)
93 return
93 return
94
94
95 data = event.as_dict()
95 data = event.as_dict()
96 post_to_webhook(data, self.settings)
96 post_to_webhook(data, self.settings)
97
97
98
98
99 @task(ignore_result=True)
99 @task(ignore_result=True)
100 def post_to_webhook(data, settings):
100 def post_to_webhook(data, settings):
101 log.debug('sending event:%s to webhook %s', data['name'], settings['url'])
101 log.debug('sending event:%s to webhook %s', data['name'], settings['url'])
102 resp = requests.post(settings['url'], json={
102 resp = requests.post(settings['url'], json={
103 'token': settings['secret_token'],
103 'token': settings['secret_token'],
104 'event': data
104 'event': data
105 })
105 })
106 resp.raise_for_status() # raise exception on a failed request
106 resp.raise_for_status() # raise exception on a failed request
General Comments 0
You need to be logged in to leave comments. Login now