##// END OF EJS Templates
rcextensions: new builtin rcextensions....
marcink -
r3133:6afdd8e7 default
parent child Browse files
Show More
@@ -0,0 +1,44 b''
1 .. _integrations-rcextensions:
2
3
4 rcextensions integrations
5 =========================
6
7
8 Since RhodeCode 4.14 release rcextensions aren't part of rhodecode-tools, and instead
9 they are shipped with the new or upgraded installations.
10
11 The rcextensions template `rcextensions.tmpl` is created in the `etc/` directory
12 of enterprise or community installation. It's always re-created and updated on upgrades.
13
14
15 Activating rcextensions
16 +++++++++++++++++++++++
17
18 To activate rcextensions simply copy or rename the created template rcextensions
19 into the path where the rhodecode.ini file is located::
20
21 pushd ~/rccontrol/enterprise-1/
22 or
23 pushd ~/rccontrol/community-1/
24
25 mv etc/rcextensions.tmpl rcextensions
26
27
28 rcextensions are loaded when |RCE| starts. So a restart is required after activation or
29 change of code in rcextensions.
30
31 Simply restart only the enterprise/community instance::
32
33 rccontrol restart enterprise-1
34 or
35 rccontrol restart community-1
36
37
38 Example usage
39 +++++++++++++
40
41
42 To see examples of usage please check the examples directory under:
43
44 https://code.rhodecode.com/rhodecode-enterprise-ce/files/stable/rhodecode/config/rcextensions/examples
@@ -0,0 +1,56 b''
1 # Copyright (C) 2016-2018 RhodeCode GmbH
2 #
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
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
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/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 """
20 rcextensions module, please edit `hooks.py` to over write hooks logic
21 """
22
23 from .hooks import (
24 _create_repo_hook,
25 _create_repo_group_hook,
26 _pre_create_user_hook,
27 _create_user_hook,
28 _delete_repo_hook,
29 _delete_user_hook,
30 _pre_push_hook,
31 _push_hook,
32 _pre_pull_hook,
33 _pull_hook,
34 _create_pull_request_hook,
35 _review_pull_request_hook,
36 _update_pull_request_hook,
37 _merge_pull_request_hook,
38 _close_pull_request_hook,
39 )
40
41 # set as module attributes, we use those to call hooks. *do not change this*
42 CREATE_REPO_HOOK = _create_repo_hook
43 CREATE_REPO_GROUP_HOOK = _create_repo_group_hook
44 PRE_CREATE_USER_HOOK = _pre_create_user_hook
45 CREATE_USER_HOOK = _create_user_hook
46 DELETE_REPO_HOOK = _delete_repo_hook
47 DELETE_USER_HOOK = _delete_user_hook
48 PRE_PUSH_HOOK = _pre_push_hook
49 PUSH_HOOK = _push_hook
50 PRE_PULL_HOOK = _pre_pull_hook
51 PULL_HOOK = _pull_hook
52 CREATE_PULL_REQUEST = _create_pull_request_hook
53 REVIEW_PULL_REQUEST = _review_pull_request_hook
54 UPDATE_PULL_REQUEST = _update_pull_request_hook
55 MERGE_PULL_REQUEST = _merge_pull_request_hook
56 CLOSE_PULL_REQUEST = _close_pull_request_hook
@@ -0,0 +1,36 b''
1 # Example to trigger a HTTP call via an HTTP helper via post_push hook
2
3
4 @has_kwargs({
5 'server_url': 'url of instance that triggered this hook',
6 'config': 'path to .ini config used',
7 'scm': 'type of version control "git", "hg", "svn"',
8 'username': 'username of actor who triggered this event',
9 'ip': 'ip address of actor who triggered this hook',
10 'action': '',
11 'repository': 'repository name',
12 'repo_store_path': 'full path to where repositories are stored',
13 'commit_ids': '',
14 'hook_type': '',
15 'user_agent': '',
16 })
17 def _push_hook(*args, **kwargs):
18 """
19 POST PUSH HOOK, this function will be executed after each push it's
20 executed after the build-in hook that RhodeCode uses for logging pushes
21 """
22
23 from .helpers import http_call, extra_fields
24 # returns list of dicts with key-val fetched from extra fields
25 repo_extra_fields = extra_fields.run(**kwargs)
26
27 if repo_extra_fields.get('endpoint_url'):
28 endpoint = repo_extra_fields['endpoint_url']
29 if endpoint:
30 data = {
31 'some_key': 'val'
32 }
33 response = http_call.run(url=endpoint, json_data=data)
34 return HookResponse(0, 'Called endpoint {}, with response {}'.format(endpoint, response))
35
36 return HookResponse(0, '')
@@ -0,0 +1,36 b''
1 # Example to trigger a CI call via an HTTP helper via post_push hook
2
3
4 @has_kwargs({
5 'server_url': 'url of instance that triggered this hook',
6 'config': 'path to .ini config used',
7 'scm': 'type of version control "git", "hg", "svn"',
8 'username': 'username of actor who triggered this event',
9 'ip': 'ip address of actor who triggered this hook',
10 'action': '',
11 'repository': 'repository name',
12 'repo_store_path': 'full path to where repositories are stored',
13 'commit_ids': '',
14 'hook_type': '',
15 'user_agent': '',
16 })
17 def _push_hook(*args, **kwargs):
18 """
19 POST PUSH HOOK, this function will be executed after each push it's
20 executed after the build-in hook that RhodeCode uses for logging pushes
21 """
22
23 from .helpers import http_call, extra_fields
24 # returns list of dicts with key-val fetched from extra fields
25 repo_extra_fields = extra_fields.run(**kwargs)
26
27 if repo_extra_fields.get('endpoint_url'):
28 endpoint = repo_extra_fields['endpoint_url']
29 if endpoint:
30 data = {
31 'some_key': 'val'
32 }
33 response = http_call.run(url=endpoint, json_data=data)
34 return HookResponse(0, 'Called endpoint {}, with response {}'.format(endpoint, response))
35
36 return HookResponse(0, '')
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
1 NO CONTENT: new file 100644
NO CONTENT: new file 100644
@@ -0,0 +1,17 b''
1 # Copyright (C) 2016-2018 RhodeCode GmbH
2 #
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
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
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/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
@@ -0,0 +1,40 b''
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2016-2018 RhodeCode GmbH
3 #
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
6 # (only), as published by the Free Software Foundation.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
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/>.
15 #
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
20 """
21 us in hooks::
22
23 from .helpers import extra_fields
24 # returns list of dicts with key-val fetched from extra fields
25 repo_extra_fields = extra_fields.run(**kwargs)
26
27 """
28
29
30 def run(*args, **kwargs):
31 from rhodecode.model.db import Repository
32 # use temp name then the main one propagated
33 repo_name = kwargs.pop('REPOSITORY', None) or kwargs['repository']
34 repo = Repository.get_by_repo_name(repo_name)
35
36 fields = {}
37 for field in repo.extra_fields:
38 fields[field.field_key] = field.get_dict()
39
40 return fields
@@ -0,0 +1,61 b''
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2016-2018 RhodeCode GmbH
3 #
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
6 # (only), as published by the Free Software Foundation.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
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/>.
15 #
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
20 """
21 Extract and serialize commits taken from a list of commit_ids. This should
22 be used in post_push hook
23
24 us in hooks::
25
26 from .helpers import extract_post_commits
27 # returns list of dicts with key-val fetched from extra fields
28 commit_list = extract_post_commits.run(**kwargs)
29 """
30 import traceback
31
32
33 def run(*args, **kwargs):
34 from rhodecode.lib.utils2 import extract_mentioned_users
35 from rhodecode.model.db import Repository
36
37 commit_ids = kwargs.get('commit_ids')
38 if not commit_ids:
39 return 0
40
41 # use temp name then the main one propagated
42 repo_name = kwargs.pop('REPOSITORY', None) or kwargs['repository']
43
44 repo = Repository.get_by_repo_name(repo_name)
45 commits = []
46
47 vcs_repo = repo.scm_instance(cache=False)
48 try:
49 for commit_id in commit_ids:
50 cs = vcs_repo.get_changeset(commit_id)
51 cs_data = cs.__json__()
52 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
53 # optionally add more logic to parse the commits, like reading extra
54 # fields of repository to read managers of reviewers ?
55 commits.append(cs_data)
56 except Exception:
57 print(traceback.format_exc())
58 # we don't send any commits when crash happens, only full list matters
59 # we short circuit then.
60 return []
61 return commits
@@ -0,0 +1,63 b''
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2016-2018 RhodeCode GmbH
3 #
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
6 # (only), as published by the Free Software Foundation.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
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/>.
15 #
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
20 """
21 us in hooks::
22
23 from .helpers import extract_pre_commits
24 # returns list of dicts with key-val fetched from extra fields
25 commit_list = extract_pre_commits.run(**kwargs)
26
27 """
28 import re
29 import collections
30
31
32 def get_hg_commits(repo, refs):
33 commits = []
34 return commits
35
36
37 def get_git_commits(repo, refs):
38 commits = []
39 return commits
40
41
42 def run(*args, **kwargs):
43 from rhodecode.model.db import Repository
44
45 vcs_type = kwargs['scm']
46 # use temp name then the main one propagated
47 repo_name = kwargs.pop('REPOSITORY', None) or kwargs['repository']
48
49 repo = Repository.get_by_repo_name(repo_name)
50 vcs_repo = repo.scm_instance(cache=False)
51
52 commits = []
53
54 for rev_data in kwargs['commit_ids']:
55 new_environ = dict((k, v) for k, v in rev_data['hg_env'])
56
57 if vcs_type == 'git':
58 commits = get_git_commits(vcs_repo, kwargs['commit_ids'])
59
60 if vcs_type == 'hg':
61 commits = get_hg_commits(vcs_repo, kwargs['commit_ids'])
62
63 return commits
@@ -0,0 +1,36 b''
1 # -*- coding: utf-8 -*-
2 # Copyright (C) 2016-2018 RhodeCode GmbH
3 #
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
6 # (only), as published by the Free Software Foundation.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
12 #
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/>.
15 #
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
20 """
21 us in hooks::
22
23 from .helpers import http_call
24 # returns response after making a POST call
25 response = http_call.run(url=url, json_data=data)
26
27 """
28
29 from rhodecode.integrations.types.base import requests_retry_call
30
31
32 def run(url, json_data, method='post'):
33 requests_session = requests_retry_call()
34 requests_session.verify = True # Verify SSL
35 resp = requests_session.post(url, json=json_data, timeout=60)
36 return resp.raise_for_status() # raise exception on a failed request
@@ -0,0 +1,431 b''
1 # Copyright (C) 2016-2018 RhodeCode GmbH
2 #
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
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
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/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 from .utils import DotDict, HookResponse, has_kwargs
20
21
22 # Config shortcut to keep, all configuration in one place
23 # Example: api_key = CONFIG.my_config.api_key
24 CONFIG = DotDict(
25 my_config=DotDict(
26 api_key='<secret>',
27 ),
28
29 )
30
31
32 @has_kwargs({
33 'repo_name': '',
34 'repo_type': '',
35 'description': '',
36 'private': '',
37 'created_on': '',
38 'enable_downloads': '',
39 'repo_id': '',
40 'user_id': '',
41 'enable_statistics': '',
42 'clone_uri': '',
43 'fork_id': '',
44 'group_id': '',
45 'created_by': ''
46 })
47 def _create_repo_hook(*args, **kwargs):
48 """
49 POST CREATE REPOSITORY HOOK. This function will be executed after
50 each repository is created. kwargs available:
51
52 """
53 return HookResponse(0, '')
54
55
56 @has_kwargs({
57 'group_name': '',
58 'group_parent_id': '',
59 'group_description': '',
60 'group_id': '',
61 'user_id': '',
62 'created_by': '',
63 'created_on': '',
64 'enable_locking': ''
65 })
66 def _create_repo_group_hook(*args, **kwargs):
67 """
68 POST CREATE REPOSITORY GROUP HOOK, this function will be
69 executed after each repository group is created. kwargs available:
70 """
71 return HookResponse(0, '')
72
73
74 @has_kwargs({
75 'username': '',
76 'password': '',
77 'email': '',
78 'firstname': '',
79 'lastname': '',
80 'active': '',
81 'admin': '',
82 'created_by': '',
83 })
84 def _pre_create_user_hook(*args, **kwargs):
85 """
86 PRE CREATE USER HOOK, this function will be executed before each
87 user is created, it returns a tuple of bool, reason.
88 If bool is False the user creation will be stopped and reason
89 will be displayed to the user.
90
91 Return HookResponse(1, reason) to block user creation
92
93 """
94
95 reason = 'allowed'
96 return HookResponse(0, reason)
97
98
99 @has_kwargs({
100 'username': '',
101 'full_name_or_username': '',
102 'full_contact': '',
103 'user_id': '',
104 'name': '',
105 'firstname': '',
106 'short_contact': '',
107 'admin': '',
108 'lastname': '',
109 'ip_addresses': '',
110 'extern_type': '',
111 'extern_name': '',
112 'email': '',
113 'api_key': '',
114 'api_keys': '',
115 'last_login': '',
116 'full_name': '',
117 'active': '',
118 'password': '',
119 'emails': '',
120 'inherit_default_permissions': '',
121 'created_by': '',
122 'created_on': '',
123 })
124 def _create_user_hook(*args, **kwargs):
125 """
126 POST CREATE USER HOOK, this function will be executed after each user is created
127 """
128 return HookResponse(0, '')
129
130
131 @has_kwargs({
132 'repo_name': '',
133 'repo_type': '',
134 'description': '',
135 'private': '',
136 'created_on': '',
137 'enable_downloads': '',
138 'repo_id': '',
139 'user_id': '',
140 'enable_statistics': '',
141 'clone_uri': '',
142 'fork_id': '',
143 'group_id': '',
144 'deleted_by': '',
145 'deleted_on': '',
146 })
147 def _delete_repo_hook(*args, **kwargs):
148 """
149 POST DELETE REPOSITORY HOOK, this function will be executed after
150 each repository deletion
151 """
152 return HookResponse(0, '')
153
154
155 @has_kwargs({
156 'username': '',
157 'full_name_or_username': '',
158 'full_contact': '',
159 'user_id': '',
160 'name': '',
161 'short_contact': '',
162 'admin': '',
163 'firstname': '',
164 'lastname': '',
165 'ip_addresses': '',
166 'email': '',
167 'api_key': '',
168 'last_login': '',
169 'full_name': '',
170 'active': '',
171 'password': '',
172 'emails': '',
173 'inherit_default_permissions': '',
174 'deleted_by': '',
175 })
176 def _delete_user_hook(*args, **kwargs):
177 """
178 POST DELETE USER HOOK, this function will be executed after each
179 user is deleted kwargs available:
180 """
181 return HookResponse(0, '')
182
183
184 # =============================================================================
185 # PUSH/PULL RELATED HOOKS
186 # =============================================================================
187 @has_kwargs({
188 'server_url': 'url of instance that triggered this hook',
189 'config': 'path to .ini config used',
190 'scm': 'type of version control "git", "hg", "svn"',
191 'username': 'username of actor who triggered this event',
192 'ip': 'ip address of actor who triggered this hook',
193 'action': '',
194 'repository': 'repository name',
195 'repo_store_path': 'full path to where repositories are stored',
196 'commit_ids': 'pre transaction metadata for commit ids',
197 'hook_type': '',
198 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
199 })
200 def _pre_push_hook(*args, **kwargs):
201 """
202 Post push hook
203 To stop version control from storing the transaction and send a message to user
204 use non-zero HookResponse with a message, e.g return HookResponse(1, 'Not allowed')
205
206 This message will be shown back to client during PUSH operation
207
208 Commit ids might look like that::
209
210 [{u'hg_env|git_env': ...,
211 u'multiple_heads': [],
212 u'name': u'default',
213 u'new_rev': u'd0befe0692e722e01d5677f27a104631cf798b69',
214 u'old_rev': u'd0befe0692e722e01d5677f27a104631cf798b69',
215 u'ref': u'',
216 u'total_commits': 2,
217 u'type': u'branch'}]
218 """
219 return HookResponse(0, '')
220
221
222 @has_kwargs({
223 'server_url': 'url of instance that triggered this hook',
224 'config': 'path to .ini config used',
225 'scm': 'type of version control "git", "hg", "svn"',
226 'username': 'username of actor who triggered this event',
227 'ip': 'ip address of actor who triggered this hook',
228 'action': '',
229 'repository': 'repository name',
230 'repo_store_path': 'full path to where repositories are stored',
231 'commit_ids': 'list of pushed commit_ids (sha1)',
232 'hook_type': '',
233 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
234 })
235 def _push_hook(*args, **kwargs):
236 """
237 POST PUSH HOOK, this function will be executed after each push it's
238 executed after the build-in hook that RhodeCode uses for logging pushes
239 """
240 return HookResponse(0, '')
241
242
243 @has_kwargs({
244 'server_url': 'url of instance that triggered this hook',
245 'repo_store_path': 'full path to where repositories are stored',
246 'config': 'path to .ini config used',
247 'scm': 'type of version control "git", "hg", "svn"',
248 'username': 'username of actor who triggered this event',
249 'ip': 'ip address of actor who triggered this hook',
250 'action': '',
251 'repository': 'repository name',
252 'hook_type': '',
253 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
254 })
255 def _pre_pull_hook(*args, **kwargs):
256 """
257 Post pull hook
258 """
259 return HookResponse(0, '')
260
261
262 @has_kwargs({
263 'server_url': 'url of instance that triggered this hook',
264 'repo_store_path': 'full path to where repositories are stored',
265 'config': 'path to .ini config used',
266 'scm': 'type of version control "git", "hg", "svn"',
267 'username': 'username of actor who triggered this event',
268 'ip': 'ip address of actor who triggered this hook',
269 'action': '',
270 'repository': 'repository name',
271 'hook_type': '',
272 'user_agent': 'Client user agent, e.g git or mercurial CLI version',
273 })
274 def _pull_hook(*args, **kwargs):
275 """
276 This hook will be executed after each code pull.
277 """
278 return HookResponse(0, '')
279
280
281 # =============================================================================
282 # PULL REQUEST RELATED HOOKS
283 # =============================================================================
284 @has_kwargs({
285 'server_url': 'url of instance that triggered this hook',
286 'config': 'path to .ini config used',
287 'scm': 'type of version control "git", "hg", "svn"',
288 'username': 'username of actor who triggered this event',
289 'ip': 'ip address of actor who triggered this hook',
290 'action': '',
291 'repository': 'repository name',
292 'pull_request_id': '',
293 'url': '',
294 'title': '',
295 'description': '',
296 'status': '',
297 'created_on': '',
298 'updated_on': '',
299 'commit_ids': '',
300 'review_status': '',
301 'mergeable': '',
302 'source': '',
303 'target': '',
304 'author': '',
305 'reviewers': '',
306 })
307 def _create_pull_request_hook(*args, **kwargs):
308 """
309 This hook will be executed after creation of a pull request.
310 """
311 return HookResponse(0, '')
312
313
314 @has_kwargs({
315 'server_url': 'url of instance that triggered this hook',
316 'config': 'path to .ini config used',
317 'scm': 'type of version control "git", "hg", "svn"',
318 'username': 'username of actor who triggered this event',
319 'ip': 'ip address of actor who triggered this hook',
320 'action': '',
321 'repository': 'repository name',
322 'pull_request_id': '',
323 'url': '',
324 'title': '',
325 'description': '',
326 'status': '',
327 'created_on': '',
328 'updated_on': '',
329 'commit_ids': '',
330 'review_status': '',
331 'mergeable': '',
332 'source': '',
333 'target': '',
334 'author': '',
335 'reviewers': '',
336 })
337 def _review_pull_request_hook(*args, **kwargs):
338 """
339 This hook will be executed after review action was made on a pull request.
340 """
341 return HookResponse(0, '')
342
343
344 @has_kwargs({
345 'server_url': 'url of instance that triggered this hook',
346 'config': 'path to .ini config used',
347 'scm': 'type of version control "git", "hg", "svn"',
348 'username': 'username of actor who triggered this event',
349 'ip': 'ip address of actor who triggered this hook',
350 'action': '',
351 'repository': 'repository name',
352 'pull_request_id': '',
353 'url': '',
354 'title': '',
355 'description': '',
356 'status': '',
357 'created_on': '',
358 'updated_on': '',
359 'commit_ids': '',
360 'review_status': '',
361 'mergeable': '',
362 'source': '',
363 'target': '',
364 'author': '',
365 'reviewers': '',
366 })
367 def _update_pull_request_hook(*args, **kwargs):
368 """
369 This hook will be executed after pull requests has been updated with new commits.
370 """
371 return HookResponse(0, '')
372
373
374 @has_kwargs({
375 'server_url': 'url of instance that triggered this hook',
376 'config': 'path to .ini config used',
377 'scm': 'type of version control "git", "hg", "svn"',
378 'username': 'username of actor who triggered this event',
379 'ip': 'ip address of actor who triggered this hook',
380 'action': '',
381 'repository': 'repository name',
382 'pull_request_id': '',
383 'url': '',
384 'title': '',
385 'description': '',
386 'status': '',
387 'created_on': '',
388 'updated_on': '',
389 'commit_ids': '',
390 'review_status': '',
391 'mergeable': '',
392 'source': '',
393 'target': '',
394 'author': '',
395 'reviewers': '',
396 })
397 def _merge_pull_request_hook(*args, **kwargs):
398 """
399 This hook will be executed after merge of a pull request.
400 """
401 return HookResponse(0, '')
402
403
404 @has_kwargs({
405 'server_url': 'url of instance that triggered this hook',
406 'config': 'path to .ini config used',
407 'scm': 'type of version control "git", "hg", "svn"',
408 'username': 'username of actor who triggered this event',
409 'ip': 'ip address of actor who triggered this hook',
410 'action': '',
411 'repository': 'repository name',
412 'pull_request_id': '',
413 'url': '',
414 'title': '',
415 'description': '',
416 'status': '',
417 'created_on': '',
418 'updated_on': '',
419 'commit_ids': '',
420 'review_status': '',
421 'mergeable': '',
422 'source': '',
423 'target': '',
424 'author': '',
425 'reviewers': '',
426 })
427 def _close_pull_request_hook(*args, **kwargs):
428 """
429 This hook will be executed after close of a pull request.
430 """
431 return HookResponse(0, '')
@@ -0,0 +1,21 b''
1 # =============================================================================
2 # END OF UTILITY FUNCTIONS HERE
3 # =============================================================================
4
5 # Additional mappings that are not present in the pygments lexers
6 # used for building stats
7 # format is {'ext':['Names']} eg. {'py':['Python']} note: there can be
8 # more than one name for extension
9 # NOTE: that this will override any mappings in LANGUAGES_EXTENSIONS_MAP
10 # build by pygments
11 EXTRA_MAPPINGS = {'html': ['Text']}
12
13 # additional lexer definitions for custom files it's overrides pygments lexers,
14 # and uses defined name of lexer to colorize the files. Format is {'ext':
15 # 'lexer_name'} List of lexers can be printed running:
16 # >> python -c "import pprint;from pygments import lexers;
17 # pprint.pprint([(x[0], x[1]) for x in lexers.get_all_lexers()]);"
18
19 EXTRA_LEXERS = {
20 'tt': 'vbnet'
21 }
@@ -0,0 +1,147 b''
1 # Copyright (C) 2016-2018 RhodeCode GmbH
2 #
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
5 # (only), as published by the Free Software Foundation.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
11 #
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/>.
14 #
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
19 import os
20 import functools
21 import collections
22
23
24 class HookResponse(object):
25 def __init__(self, status, output):
26 self.status = status
27 self.output = output
28
29 def __add__(self, other):
30 other_status = getattr(other, 'status', 0)
31 new_status = max(self.status, other_status)
32 other_output = getattr(other, 'output', '')
33 new_output = self.output + other_output
34
35 return HookResponse(new_status, new_output)
36
37 def __bool__(self):
38 return self.status == 0
39
40
41 class DotDict(dict):
42
43 def __contains__(self, k):
44 try:
45 return dict.__contains__(self, k) or hasattr(self, k)
46 except:
47 return False
48
49 # only called if k not found in normal places
50 def __getattr__(self, k):
51 try:
52 return object.__getattribute__(self, k)
53 except AttributeError:
54 try:
55 return self[k]
56 except KeyError:
57 raise AttributeError(k)
58
59 def __setattr__(self, k, v):
60 try:
61 object.__getattribute__(self, k)
62 except AttributeError:
63 try:
64 self[k] = v
65 except:
66 raise AttributeError(k)
67 else:
68 object.__setattr__(self, k, v)
69
70 def __delattr__(self, k):
71 try:
72 object.__getattribute__(self, k)
73 except AttributeError:
74 try:
75 del self[k]
76 except KeyError:
77 raise AttributeError(k)
78 else:
79 object.__delattr__(self, k)
80
81 def toDict(self):
82 return unserialize(self)
83
84 def __repr__(self):
85 keys = list(self.keys())
86 keys.sort()
87 args = ', '.join(['%s=%r' % (key, self[key]) for key in keys])
88 return '%s(%s)' % (self.__class__.__name__, args)
89
90 @staticmethod
91 def fromDict(d):
92 return serialize(d)
93
94
95 def serialize(x):
96 if isinstance(x, dict):
97 return DotDict((k, serialize(v)) for k, v in x.items())
98 elif isinstance(x, (list, tuple)):
99 return type(x)(serialize(v) for v in x)
100 else:
101 return x
102
103
104 def unserialize(x):
105 if isinstance(x, dict):
106 return dict((k, unserialize(v)) for k, v in x.items())
107 elif isinstance(x, (list, tuple)):
108 return type(x)(unserialize(v) for v in x)
109 else:
110 return x
111
112
113 def _verify_kwargs(func_name, expected_parameters, kwargs):
114 """
115 Verify that exactly `expected_parameters` are passed in as `kwargs`.
116 """
117 expected_parameters = set(expected_parameters)
118 kwargs_keys = set(kwargs.keys())
119 if kwargs_keys != expected_parameters:
120 missing_kwargs = expected_parameters - kwargs_keys
121 unexpected_kwargs = kwargs_keys - expected_parameters
122 raise AssertionError(
123 "func:%s: missing parameters: %r, unexpected parameters: %s" %
124 (func_name, missing_kwargs, unexpected_kwargs))
125
126
127 def has_kwargs(required_args):
128 """
129 decorator to verify extension calls arguments.
130
131 :param required_args:
132 """
133 def wrap(func):
134 def wrapper(*args, **kwargs):
135 _verify_kwargs(func.func_name, required_args.keys(), kwargs)
136 # in case there's `calls` defined on module we store the data
137 maybe_log_call(func.func_name, args, kwargs)
138 return func(*args, **kwargs)
139 return wrapper
140 return wrap
141
142
143 def maybe_log_call(name, args, kwargs):
144 from rhodecode.config import rcextensions
145 if hasattr(rcextensions, 'calls'):
146 calls = rcextensions.calls
147 calls[name].append((args, kwargs))
@@ -201,6 +201,9 b' let'
201 cp configs/production.ini $out/etc
201 cp configs/production.ini $out/etc
202 echo "[DONE]: saved enterprise-ce production.ini into $out/etc"
202 echo "[DONE]: saved enterprise-ce production.ini into $out/etc"
203
203
204 cp -r rhodecode/config/rcextensions $out/etc/rcextensions.tmpl
205 echo "[DONE]: saved enterprise-ce rcextensions into $out/etc/rcextensions.tmpl"
206
204 # python based programs need to be wrapped
207 # python based programs need to be wrapped
205 mkdir -p $out/bin
208 mkdir -p $out/bin
206
209
@@ -14,12 +14,23 b' so to clarify what is meant each time, r'
14 between software components and can be used to trigger plugins, or their
14 between software components and can be used to trigger plugins, or their
15 extensions.
15 extensions.
16
16
17 .. toctree::
17
18 Hooks
19 -----
20
21 Within |RCM| there are two types of supported hooks.
18
22
19 rcx
23 * **Internal built-in hooks**: The internal |hg|, |git| or |svn| hooks are
20 install-ext
24 triggered by different VCS operations, like push, pull,
21 config-ext
25 or clone and are non-configurable, but you can add your own VCS hooks,
22 extensions
26 see :ref:`custom-hooks`.
23 hooks
27 * **Custom rcextensions hooks**: User defined hooks centre around the lifecycle of
24 full-blown-example
28 certain actions such are |repo| creation, user creation etc. The actions
25 int-slack
29 these hooks trigger can be rejected based on the API permissions of the
30 user calling them.
31
32 On instructions how to use the custom `rcextensions`
33 see :ref:`integrations-rcextensions` section.
34
35
36
@@ -3,7 +3,7 b''
3 Integrations
3 Integrations
4 ------------
4 ------------
5
5
6 Rhodecode supports integrations with external services for various events,
6 |RCE| supports integrations with external services for various events,
7 such as commit pushes and pull requests. Multiple integrations of the same type
7 such as commit pushes and pull requests. Multiple integrations of the same type
8 can be added at the same time; this is useful for posting different events to
8 can be added at the same time; this is useful for posting different events to
9 different Slack channels, for example.
9 different Slack channels, for example.
@@ -11,18 +11,20 b' different Slack channels, for example.'
11 Supported integrations
11 Supported integrations
12 ^^^^^^^^^^^^^^^^^^^^^^
12 ^^^^^^^^^^^^^^^^^^^^^^
13
13
14 ============================ ============ =====================================
14 ================================ ============ ========================================
15 Type/Name |RC| Edition Description
15 Type/Name |RC| Edition Description
16 ============================ ============ =====================================
16 ================================ ============ ========================================
17 :ref:`integrations-slack` |RCCEshort| https://slack.com/
17 :ref:`integrations-webhook` |RCCEshort| Trigger events as `json` to a custom url
18 :ref:`integrations-hipchat` |RCCEshort| https://www.hipchat.com/
18 :ref:`integrations-slack` |RCCEshort| Integrate with https://slack.com/
19 :ref:`integrations-webhook` |RCCEshort| POST events as `json` to a custom url
19 :ref:`integrations-hipchat` |RCCEshort| Integrate with https://www.hipchat.com/
20 :ref:`integrations-email` |RCCEshort| Send repo push commits by email
20 :ref:`integrations-ci` |RCCEshort| Trigger Builds for Common CI Systems
21 :ref:`integrations-ci` |RCCEshort| Trigger Builds for Common CI Systems
21 :ref:`integrations-email` |RCCEshort| Send repo push commits by email
22 :ref:`integrations-rcextensions` |RCCEshort| Advanced low-level integration framework
23
22 :ref:`integrations-jenkins` |RCEEshort| Trigger Builds for Jenkins CI System
24 :ref:`integrations-jenkins` |RCEEshort| Trigger Builds for Jenkins CI System
23 :ref:`integrations-redmine` |RCEEshort| Close/Resolve/Reference Redmine issues
25 :ref:`integrations-redmine` |RCEEshort| Close/Resolve/Reference Redmine issues
24 :ref:`integrations-jira` |RCEEshort| Close/Resolve/Reference JIRA issues
26 :ref:`integrations-jira` |RCEEshort| Close/Resolve/Reference JIRA issues
25 ============================ ============ =====================================
27 ================================ ============ ========================================
26
28
27 .. _creating-integrations:
29 .. _creating-integrations:
28
30
@@ -55,3 +57,4 b' See pages specific to each type of integ'
55 email
57 email
56 ci
58 ci
57 jenkins
59 jenkins
60 integrations-rcextensions
@@ -273,107 +273,8 b' Use this to create or update a |RCE| con'
273 rhodecode-extensions
273 rhodecode-extensions
274 --------------------
274 --------------------
275
275
276 |RCT| adds additional mapping for :ref:`indexing-ref`, statistics, and adds
276 The `rcextensions` since version 4.14 are now shipped together with |RCE| please check
277 additional code for push/pull/create/delete |repo| hooks. These hooks can be
277 the using :ref:`integrations-rcextensions` section.
278 used to send signals to build-bots such as jenkins. Options:
279
280 .. rst-class:: dl-horizontal
281
282 \-c, - -config <config_file>
283 Create a configuration file. The default file is created
284 in ``~/.rhoderc``
285
286 \-h, - -help
287 Show help messages.
288
289 \-F, - -format {json,pretty}
290 Set the formatted representation.
291
292 \-I, - -install-dir <str>
293 Set the location of the |RCE| installation. The default location is
294 :file:`/home/{user}/.rccontrol/`.
295
296 \ - -ini-file <str>
297 Path to the :file:`rhodecode.ini` file for that instance.
298
299 \ - -instance-name <instance-id>
300 Set the instance name.
301
302 \ - -plugins
303 Add plugins to your |RCE| installation. See the
304 :ref:`extensions-hooks-ref` section for more details.
305
306 \ - -version
307 Display your |RCT| version.
308
309
310 Once installed, you will see a :file:`rcextensions` folder in the instance
311 directory, for example :file:`home/{user}/.rccontrol/{instance-id}/rcextensions`
312
313 To install ``rcextensions``, use the following example:
314
315 .. code-block:: bash
316
317 # install extensions on the given instance
318 # If using virtualenv prior to RCE 350
319 (venv)$ rhodecode-extensions --instance-name=enterprise-1 \
320 --ini-file=rhodecode.ini
321 Writen new extensions file to rcextensions
322
323 # install extensions with additional plugins on the given instance
324 (venv)$ rhodecode-extensions --instance-name=enterprise-1 \
325 --ini-file=rhodecode.ini --plugins
326 Writen new extensions file to rcextensions
327
328 # installing extensions from 350 onwards
329 # as they are packaged with RCE
330 $ .rccontrol/enterprise-4/profile/bin/rhodecode-extensions --plugins \
331 --instance-name=enterprise-4 --ini-file=rhodecode.ini
332
333 Writen new extensions file to rcextensions
334
335 See the new extensions inside this directory for more details about the
336 additional hooks available, for example see the ``push_post.py`` file.
337
338 .. code-block:: python
339
340 import urllib
341 import urllib2
342
343 def run(*args, **kwargs):
344 """
345 Extra params
346
347 :param URL: url to send the data to
348 """
349
350 url = kwargs.pop('URL', None)
351 if url:
352 from rhodecode.lib.compat import json
353 from rhodecode.model.db import Repository
354
355 repo = Repository.get_by_repo_name(kwargs['repository'])
356 changesets = []
357 vcs_repo = repo.scm_instance_no_cache()
358 for r in kwargs['pushed_revs']:
359 cs = vcs_repo.get_changeset(r)
360 changesets.append(json.dumps(cs))
361
362 kwargs['pushed_revs'] = changesets
363 headers = {
364 'User-Agent': 'RhodeCode-SCM web hook',
365 'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
366 'Accept': 'text/javascript, text/html, application/xml, '
367 'text/xml, */*',
368 'Accept-Encoding': 'gzip,deflate,sdch',
369 }
370
371 data = kwargs
372 data = urllib.urlencode(data)
373 req = urllib2.Request(url, data, headers)
374 response = urllib2.urlopen(req)
375 response.read()
376 return 0
377
278
378
279
379 rhodecode-gist
280 rhodecode-gist
@@ -54,23 +54,13 b' packaged with |RCE| by default.'
54
54
55 .. code-block:: bash
55 .. code-block:: bash
56
56
57 $ .rccontrol/enterprise-4/profile/bin/rhodecode-extensions --plugins \
57 $ .rccontrol/enterprise-4/profile/bin/rhodecode-api --instance-name=enterprise-4 get_ip [11:56:57 on 05/10/2018]
58 --instance-name=enterprise-4 --ini-file=rhodecode.ini
59
58
60 Writen new extensions file to rcextensions
59 {
61 Copied hipchat_push_notify.py plugin to rcextensions
60 "error": null,
62 Copied jira_pr_flow.py plugin to rcextensions
61 "id": 1000,
63 Copied default_reviewers.py plugin to rcextensions
62 "result": {
64 Copied extract_commits.py plugin to rcextensions
63 "server_ip_addr": "1.2.3.4",
65 Copied extract_issues.py plugin to rcextensions
64 "user_ips": []
66 Copied redmine_pr_flow.py plugin to rcextensions
65 }
67 Copied extra_fields.py plugin to rcextensions
66 }
68 Copied jira_smart_commits.py plugin to rcextensions
69 Copied http_notify.py plugin to rcextensions
70 Copied slack_push_notify.py plugin to rcextensions
71 Copied slack_message.py plugin to rcextensions
72 Copied extract_jira_issues.py plugin to rcextensions
73 Copied extract_redmine_issues.py plugin to rcextensions
74 Copied redmine_smart_commits.py plugin to rcextensions
75 Copied send_mail.py plugin to rcextensions
76
@@ -578,11 +578,11 b' class TestPullrequestsView(object):'
578 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
578 assert actions[-1].action_data['commit_ids'] == pr_commit_ids
579
579
580 # Check post_push rcextension was really executed
580 # Check post_push rcextension was really executed
581 push_calls = rhodecode.EXTENSIONS.calls['post_push']
581 push_calls = rhodecode.EXTENSIONS.calls['_push_hook']
582 assert len(push_calls) == 1
582 assert len(push_calls) == 1
583 unused_last_call_args, last_call_kwargs = push_calls[0]
583 unused_last_call_args, last_call_kwargs = push_calls[0]
584 assert last_call_kwargs['action'] == 'push'
584 assert last_call_kwargs['action'] == 'push'
585 assert last_call_kwargs['pushed_revs'] == pr_commit_ids
585 assert last_call_kwargs['commit_ids'] == pr_commit_ids
586
586
587 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
587 def test_merge_pull_request_disabled(self, pr_util, csrf_token):
588 pull_request = pr_util.create_pull_request(mergeable=False)
588 pull_request = pr_util.create_pull_request(mergeable=False)
@@ -39,7 +39,21 b' from rhodecode.model.db import Repositor'
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 HookResponse = collections.namedtuple('HookResponse', ('status', 'output'))
42 class HookResponse(object):
43 def __init__(self, status, output):
44 self.status = status
45 self.output = output
46
47 def __add__(self, other):
48 other_status = getattr(other, 'status', 0)
49 new_status = max(self.status, other_status)
50 other_output = getattr(other, 'output', '')
51 new_output = self.output + other_output
52
53 return HookResponse(new_status, new_output)
54
55 def __bool__(self):
56 return self.status == 0
43
57
44
58
45 def is_shadow_repo(extras):
59 def is_shadow_repo(extras):
@@ -110,6 +124,7 b' def pre_push(extras):'
110 else:
124 else:
111 raise _http_ret
125 raise _http_ret
112
126
127 hook_response = ''
113 if not is_shadow_repo(extras):
128 if not is_shadow_repo(extras):
114 if extras.commit_ids and extras.check_branch_perms:
129 if extras.commit_ids and extras.check_branch_perms:
115
130
@@ -152,11 +167,12 b' def pre_push(extras):'
152
167
153 # Propagate to external components. This is done after checking the
168 # Propagate to external components. This is done after checking the
154 # lock, for consistent behavior.
169 # lock, for consistent behavior.
155 pre_push_extension(repo_store_path=Repository.base_path(), **extras)
170 hook_response = pre_push_extension(
171 repo_store_path=Repository.base_path(), **extras)
156 events.trigger(events.RepoPrePushEvent(
172 events.trigger(events.RepoPrePushEvent(
157 repo_name=extras.repository, extras=extras))
173 repo_name=extras.repository, extras=extras))
158
174
159 return HookResponse(0, output)
175 return HookResponse(0, output) + hook_response
160
176
161
177
162 def pre_pull(extras):
178 def pre_pull(extras):
@@ -182,12 +198,15 b' def pre_pull(extras):'
182
198
183 # Propagate to external components. This is done after checking the
199 # Propagate to external components. This is done after checking the
184 # lock, for consistent behavior.
200 # lock, for consistent behavior.
201 hook_response = ''
185 if not is_shadow_repo(extras):
202 if not is_shadow_repo(extras):
186 pre_pull_extension(**extras)
203 extras.hook_type = extras.hook_type or 'pre_pull'
204 hook_response = pre_pull_extension(
205 repo_store_path=Repository.base_path(), **extras)
187 events.trigger(events.RepoPrePullEvent(
206 events.trigger(events.RepoPrePullEvent(
188 repo_name=extras.repository, extras=extras))
207 repo_name=extras.repository, extras=extras))
189
208
190 return HookResponse(0, output)
209 return HookResponse(0, output) + hook_response
191
210
192
211
193 def post_pull(extras):
212 def post_pull(extras):
@@ -198,16 +217,9 b' def post_pull(extras):'
198 ip_addr=extras.ip)
217 ip_addr=extras.ip)
199 repo = audit_logger.RepoWrap(repo_name=extras.repository)
218 repo = audit_logger.RepoWrap(repo_name=extras.repository)
200 audit_logger.store(
219 audit_logger.store(
201 'user.pull', action_data={
220 'user.pull', action_data={'user_agent': extras.user_agent},
202 'user_agent': extras.user_agent},
203 user=audit_user, repo=repo, commit=True)
221 user=audit_user, repo=repo, commit=True)
204
222
205 # Propagate to external components.
206 if not is_shadow_repo(extras):
207 post_pull_extension(**extras)
208 events.trigger(events.RepoPullEvent(
209 repo_name=extras.repository, extras=extras))
210
211 output = ''
223 output = ''
212 # make lock is a tri state False, True, None. We only make lock on True
224 # make lock is a tri state False, True, None. We only make lock on True
213 if extras.make_lock is True and not is_shadow_repo(extras):
225 if extras.make_lock is True and not is_shadow_repo(extras):
@@ -227,7 +239,16 b' def post_pull(extras):'
227 # 2xx Codes don't raise exceptions
239 # 2xx Codes don't raise exceptions
228 output += _http_ret.title
240 output += _http_ret.title
229
241
230 return HookResponse(0, output)
242 # Propagate to external components.
243 hook_response = ''
244 if not is_shadow_repo(extras):
245 extras.hook_type = extras.hook_type or 'post_pull'
246 hook_response = post_pull_extension(
247 repo_store_path=Repository.base_path(), **extras)
248 events.trigger(events.RepoPullEvent(
249 repo_name=extras.repository, extras=extras))
250
251 return HookResponse(0, output) + hook_response
231
252
232
253
233 def post_push(extras):
254 def post_push(extras):
@@ -245,16 +266,6 b' def post_push(extras):'
245 user=audit_user, repo=repo, commit=True)
266 user=audit_user, repo=repo, commit=True)
246
267
247 # Propagate to external components.
268 # Propagate to external components.
248 if not is_shadow_repo(extras):
249 post_push_extension(
250 repo_store_path=Repository.base_path(),
251 pushed_revs=commit_ids,
252 **extras)
253 events.trigger(events.RepoPushEvent(
254 repo_name=extras.repository,
255 pushed_commit_ids=commit_ids,
256 extras=extras))
257
258 output = ''
269 output = ''
259 # make lock is a tri state False, True, None. We only release lock on False
270 # make lock is a tri state False, True, None. We only release lock on False
260 if extras.make_lock is False and not is_shadow_repo(extras):
271 if extras.make_lock is False and not is_shadow_repo(extras):
@@ -285,8 +296,16 b' def post_push(extras):'
285 output += 'RhodeCode: open pull request link: {}\n'.format(
296 output += 'RhodeCode: open pull request link: {}\n'.format(
286 tmpl.format(ref_type='bookmark', ref_name=book_name))
297 tmpl.format(ref_type='bookmark', ref_name=book_name))
287
298
299 hook_response = ''
300 if not is_shadow_repo(extras):
301 hook_response = post_push_extension(
302 repo_store_path=Repository.base_path(),
303 **extras)
304 events.trigger(events.RepoPushEvent(
305 repo_name=extras.repository, pushed_commit_ids=commit_ids, extras=extras))
306
288 output += 'RhodeCode: push completed\n'
307 output += 'RhodeCode: push completed\n'
289 return HookResponse(0, output)
308 return HookResponse(0, output) + hook_response
290
309
291
310
292 def _locked_by_explanation(repo_name, user_name, reason):
311 def _locked_by_explanation(repo_name, user_name, reason):
@@ -299,8 +318,10 b' def _locked_by_explanation(repo_name, us'
299 def check_allowed_create_user(user_dict, created_by, **kwargs):
318 def check_allowed_create_user(user_dict, created_by, **kwargs):
300 # pre create hooks
319 # pre create hooks
301 if pre_create_user.is_active():
320 if pre_create_user.is_active():
302 allowed, reason = pre_create_user(created_by=created_by, **user_dict)
321 hook_result = pre_create_user(created_by=created_by, **user_dict)
322 allowed = hook_result.status == 0
303 if not allowed:
323 if not allowed:
324 reason = hook_result.output
304 raise UserCreationError(reason)
325 raise UserCreationError(reason)
305
326
306
327
@@ -319,8 +340,15 b' class ExtensionCallback(object):'
319
340
320 def __call__(self, *args, **kwargs):
341 def __call__(self, *args, **kwargs):
321 log.debug('Calling extension callback for %s', self._hook_name)
342 log.debug('Calling extension callback for %s', self._hook_name)
343 kwargs_to_pass = {}
344 for key in self._kwargs_keys:
345 try:
346 kwargs_to_pass[key] = kwargs[key]
347 except KeyError:
348 log.error('Failed to fetch %s key. Expected keys: %s',
349 key, self._kwargs_keys)
350 raise
322
351
323 kwargs_to_pass = dict((key, kwargs[key]) for key in self._kwargs_keys)
324 # backward compat for removed api_key for old hooks. THis was it works
352 # backward compat for removed api_key for old hooks. THis was it works
325 # with older rcextensions that require api_key present
353 # with older rcextensions that require api_key present
326 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
354 if self._hook_name in ['CREATE_USER_HOOK', 'DELETE_USER_HOOK']:
@@ -343,28 +371,28 b' pre_pull_extension = ExtensionCallback('
343 hook_name='PRE_PULL_HOOK',
371 hook_name='PRE_PULL_HOOK',
344 kwargs_keys=(
372 kwargs_keys=(
345 'server_url', 'config', 'scm', 'username', 'ip', 'action',
373 'server_url', 'config', 'scm', 'username', 'ip', 'action',
346 'repository'))
374 'repository', 'hook_type', 'user_agent', 'repo_store_path',))
347
375
348
376
349 post_pull_extension = ExtensionCallback(
377 post_pull_extension = ExtensionCallback(
350 hook_name='PULL_HOOK',
378 hook_name='PULL_HOOK',
351 kwargs_keys=(
379 kwargs_keys=(
352 'server_url', 'config', 'scm', 'username', 'ip', 'action',
380 'server_url', 'config', 'scm', 'username', 'ip', 'action',
353 'repository'))
381 'repository', 'hook_type', 'user_agent', 'repo_store_path',))
354
382
355
383
356 pre_push_extension = ExtensionCallback(
384 pre_push_extension = ExtensionCallback(
357 hook_name='PRE_PUSH_HOOK',
385 hook_name='PRE_PUSH_HOOK',
358 kwargs_keys=(
386 kwargs_keys=(
359 'server_url', 'config', 'scm', 'username', 'ip', 'action',
387 'server_url', 'config', 'scm', 'username', 'ip', 'action',
360 'repository', 'repo_store_path', 'commit_ids'))
388 'repository', 'repo_store_path', 'commit_ids', 'hook_type', 'user_agent',))
361
389
362
390
363 post_push_extension = ExtensionCallback(
391 post_push_extension = ExtensionCallback(
364 hook_name='PUSH_HOOK',
392 hook_name='PUSH_HOOK',
365 kwargs_keys=(
393 kwargs_keys=(
366 'server_url', 'config', 'scm', 'username', 'ip', 'action',
394 'server_url', 'config', 'scm', 'username', 'ip', 'action',
367 'repository', 'repo_store_path', 'pushed_revs'))
395 'repository', 'repo_store_path', 'commit_ids', 'hook_type', 'user_agent',))
368
396
369
397
370 pre_create_user = ExtensionCallback(
398 pre_create_user = ExtensionCallback(
@@ -47,7 +47,7 b' def _get_rc_scm_extras(username, repo_na'
47
47
48
48
49 def trigger_post_push_hook(
49 def trigger_post_push_hook(
50 username, action, repo_name, repo_alias, commit_ids):
50 username, action, hook_type, repo_name, repo_alias, commit_ids):
51 """
51 """
52 Triggers push action hooks
52 Triggers push action hooks
53
53
@@ -59,6 +59,7 b' def trigger_post_push_hook('
59 """
59 """
60 extras = _get_rc_scm_extras(username, repo_name, repo_alias, action)
60 extras = _get_rc_scm_extras(username, repo_name, repo_alias, action)
61 extras.commit_ids = commit_ids
61 extras.commit_ids = commit_ids
62 extras.hook_type = hook_type
62 hooks_base.post_push(extras)
63 hooks_base.post_push(extras)
63
64
64
65
@@ -28,6 +28,7 b' import json'
28 import logging
28 import logging
29 import os
29 import os
30 import re
30 import re
31 import sys
31 import shutil
32 import shutil
32 import tempfile
33 import tempfile
33 import traceback
34 import traceback
@@ -43,7 +44,6 b' from mako import exceptions'
43 from pyramid.threadlocal import get_current_registry
44 from pyramid.threadlocal import get_current_registry
44 from rhodecode.lib.request import Request
45 from rhodecode.lib.request import Request
45
46
46 from rhodecode.lib.fakemod import create_module
47 from rhodecode.lib.vcs.backends.base import Config
47 from rhodecode.lib.vcs.backends.base import Config
48 from rhodecode.lib.vcs.exceptions import VCSError
48 from rhodecode.lib.vcs.exceptions import VCSError
49 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
49 from rhodecode.lib.vcs.utils.helpers import get_scm, get_scm_backend
@@ -631,21 +631,21 b' def load_rcextensions(root_path):'
631 import rhodecode
631 import rhodecode
632 from rhodecode.config import conf
632 from rhodecode.config import conf
633
633
634 path = os.path.join(root_path, 'rcextensions', '__init__.py')
634 path = os.path.join(root_path)
635 if os.path.isfile(path):
635 sys.path.append(path)
636 rcext = create_module('rc', path)
636 try:
637 EXT = rhodecode.EXTENSIONS = rcext
637 rcextensions = __import__('rcextensions')
638 log.debug('Found rcextensions now loading %s...', rcext)
638 except ImportError:
639 log.warn('Unable to load rcextensions from %s', path)
640 rcextensions = None
641
642 if rcextensions:
643 log.debug('Found rcextensions module loaded %s...', rcextensions)
644 rhodecode.EXTENSIONS = rcextensions
639
645
640 # Additional mappings that are not present in the pygments lexers
646 # Additional mappings that are not present in the pygments lexers
641 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
647 conf.LANGUAGES_EXTENSIONS_MAP.update(
642
648 getattr(rhodecode.EXTENSIONS, 'EXTRA_MAPPINGS', {}))
643 # auto check if the module is not missing any data, set to default if is
644 # this will help autoupdate new feature of rcext module
645 #from rhodecode.config import rcextensions
646 #for k in dir(rcextensions):
647 # if not k.startswith('_') and not hasattr(EXT, k):
648 # setattr(EXT, k, getattr(rcextensions, k))
649
649
650
650
651 def get_custom_lexer(extension):
651 def get_custom_lexer(extension):
@@ -590,6 +590,7 b' class PullRequestModel(BaseModel):'
590
590
591 def merge_repo(self, pull_request, user, extras):
591 def merge_repo(self, pull_request, user, extras):
592 log.debug("Merging pull request %s", pull_request.pull_request_id)
592 log.debug("Merging pull request %s", pull_request.pull_request_id)
593 extras['user_agent'] = 'internal-merge'
593 merge_state = self._merge_pull_request(pull_request, user, extras)
594 merge_state = self._merge_pull_request(pull_request, user, extras)
594 if merge_state.executed:
595 if merge_state.executed:
595 log.debug(
596 log.debug(
@@ -465,8 +465,8 b' class ScmModel(BaseModel):'
465
465
466 # We trigger the post-push action
466 # We trigger the post-push action
467 hooks_utils.trigger_post_push_hook(
467 hooks_utils.trigger_post_push_hook(
468 username=user.username, action='push_local', repo_name=repo_name,
468 username=user.username, action='push_local', hook_type='post_push',
469 repo_alias=repo.alias, commit_ids=[tip.raw_id])
469 repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id])
470 return tip
470 return tip
471
471
472 def _sanitize_path(self, f_path):
472 def _sanitize_path(self, f_path):
@@ -644,6 +644,7 b' class ScmModel(BaseModel):'
644 hooks_utils.trigger_post_push_hook(
644 hooks_utils.trigger_post_push_hook(
645 username=user.username, action='push_local',
645 username=user.username, action='push_local',
646 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
646 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
647 hook_type='post_push',
647 commit_ids=[tip.raw_id])
648 commit_ids=[tip.raw_id])
648 return tip
649 return tip
649
650
@@ -708,7 +709,7 b' class ScmModel(BaseModel):'
708
709
709 if trigger_push_hook:
710 if trigger_push_hook:
710 hooks_utils.trigger_post_push_hook(
711 hooks_utils.trigger_post_push_hook(
711 username=user.username, action='push_local',
712 username=user.username, action='push_local', hook_type='post_push',
712 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
713 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
713 commit_ids=[tip.raw_id])
714 commit_ids=[tip.raw_id])
714
715
@@ -768,7 +769,7 b' class ScmModel(BaseModel):'
768 self.mark_for_invalidation(repo.repo_name)
769 self.mark_for_invalidation(repo.repo_name)
769 if trigger_push_hook:
770 if trigger_push_hook:
770 hooks_utils.trigger_post_push_hook(
771 hooks_utils.trigger_post_push_hook(
771 username=user.username, action='push_local',
772 username=user.username, action='push_local', hook_type='post_push',
772 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
773 repo_name=repo.repo_name, repo_alias=scm_instance.alias,
773 commit_ids=[tip.raw_id])
774 commit_ids=[tip.raw_id])
774 return tip
775 return tip
@@ -45,9 +45,10 b' def scm_extras(user_regular, repo_stub):'
45 'repo_store': '',
45 'repo_store': '',
46 'server_url': 'http://example.com',
46 'server_url': 'http://example.com',
47 'make_lock': None,
47 'make_lock': None,
48 'user-agent': 'some-client',
48 'user_agent': 'some-client',
49 'locked_by': [None],
49 'locked_by': [None],
50 'commit_ids': ['a' * 40] * 3,
50 'commit_ids': ['a' * 40] * 3,
51 'hook_type': 'scm_extras_test',
51 'is_shadow_repo': False,
52 'is_shadow_repo': False,
52 })
53 })
53 return extras
54 return extras
@@ -38,6 +38,7 b' def test_post_push_truncates_commits(use'
38 'user_agent': 'some-client',
38 'user_agent': 'some-client',
39 'locked_by': [None],
39 'locked_by': [None],
40 'commit_ids': ['abcde12345' * 4] * 30000,
40 'commit_ids': ['abcde12345' * 4] * 30000,
41 'hook_type': 'large_push_test_type',
41 'is_shadow_repo': False,
42 'is_shadow_repo': False,
42 }
43 }
43 extras = utils2.AttributeDict(extras)
44 extras = utils2.AttributeDict(extras)
@@ -76,6 +77,7 b' def hook_extras(user_regular, repo_stub)'
76 'user_agent': 'some-client',
77 'user_agent': 'some-client',
77 'locked_by': [None],
78 'locked_by': [None],
78 'commit_ids': [],
79 'commit_ids': [],
80 'hook_type': 'test_type',
79 'is_shadow_repo': False,
81 'is_shadow_repo': False,
80 })
82 })
81 return extras
83 return extras
@@ -92,7 +94,12 b' def test_hooks_propagate(func, extension'
92 Tests that our hook code propagates to rhodecode extensions and triggers
94 Tests that our hook code propagates to rhodecode extensions and triggers
93 the appropriate event.
95 the appropriate event.
94 """
96 """
95 extension_mock = mock.Mock()
97 class ExtensionMock(mock.Mock):
98 @property
99 def output(self):
100 return 'MOCK'
101
102 extension_mock = ExtensionMock()
96 events_mock = mock.Mock()
103 events_mock = mock.Mock()
97 patches = {
104 patches = {
98 'Repository': mock.Mock(),
105 'Repository': mock.Mock(),
@@ -393,6 +393,7 b' class TestIntegrationMerge(object):'
393 def test_merge_triggers_push_hooks(
393 def test_merge_triggers_push_hooks(
394 self, pr_util, user_admin, capture_rcextensions, merge_extras,
394 self, pr_util, user_admin, capture_rcextensions, merge_extras,
395 extra_config):
395 extra_config):
396
396 pull_request = pr_util.create_pull_request(
397 pull_request = pr_util.create_pull_request(
397 approved=True, mergeable=True)
398 approved=True, mergeable=True)
398 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
399 # TODO: johbo: Needed for sqlite, try to find an automatic way for it
@@ -404,8 +405,8 b' class TestIntegrationMerge(object):'
404 pull_request, user_admin, extras=merge_extras)
405 pull_request, user_admin, extras=merge_extras)
405
406
406 assert merge_state.executed
407 assert merge_state.executed
407 assert 'pre_push' in capture_rcextensions
408 assert '_pre_push_hook' in capture_rcextensions
408 assert 'post_push' in capture_rcextensions
409 assert '_push_hook' in capture_rcextensions
409
410
410 def test_merge_can_be_rejected_by_pre_push_hook(
411 def test_merge_can_be_rejected_by_pre_push_hook(
411 self, pr_util, user_admin, capture_rcextensions, merge_extras):
412 self, pr_util, user_admin, capture_rcextensions, merge_extras):
@@ -154,10 +154,11 b' def activate_example_rcextensions(reques'
154 """
154 """
155 Patch in an example rcextensions module which verifies passed in kwargs.
155 Patch in an example rcextensions module which verifies passed in kwargs.
156 """
156 """
157 from rhodecode.tests.other import example_rcextensions
157 from rhodecode.config import rcextensions
158
158
159 old_extensions = rhodecode.EXTENSIONS
159 old_extensions = rhodecode.EXTENSIONS
160 rhodecode.EXTENSIONS = example_rcextensions
160 rhodecode.EXTENSIONS = rcextensions
161 rhodecode.EXTENSIONS.calls = collections.defaultdict(list)
161
162
162 @request.addfinalizer
163 @request.addfinalizer
163 def cleanup():
164 def cleanup():
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (823 lines changed) Show them Hide them
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
This diff has been collapsed as it changes many lines, (521 lines changed) Show them Hide them
General Comments 0
You need to be logged in to leave comments. Login now