##// END OF EJS Templates
events: added more validation of repo type events
super-admin -
r5124:e0800033 default
parent child Browse files
Show More
@@ -1,436 +1,440 b''
1 1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import collections
20 20 import logging
21 21 import datetime
22 22
23 23 from rhodecode.translation import lazy_ugettext
24 24 from rhodecode.model.db import User, Repository, Session
25 25 from rhodecode.events.base import RhodeCodeIntegrationEvent
26 26 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
27 27
28 28 log = logging.getLogger(__name__)
29 29
30 30
31 31 def _commits_as_dict(event, commit_ids, repos):
32 32 """
33 33 Helper function to serialize commit_ids
34 34
35 35 :param event: class calling this method
36 36 :param commit_ids: commits to get
37 37 :param repos: list of repos to check
38 38 """
39 39 from rhodecode.lib.utils2 import extract_mentioned_users
40 40 from rhodecode.lib.helpers import (
41 41 urlify_commit_message, process_patterns, chop_at_smart)
42 42 from rhodecode.model.repo import RepoModel
43 43
44 44 if not repos:
45 45 raise Exception('no repo defined')
46 46
47 47 if not isinstance(repos, (tuple, list)):
48 48 repos = [repos]
49 49
50 50 if not commit_ids:
51 51 return []
52 52
53 53 needed_commits = list(commit_ids)
54 54
55 55 commits = []
56 56 reviewers = []
57 57 for repo in repos:
58 58 if not needed_commits:
59 59 return commits # return early if we have the commits we need
60 60
61 61 vcs_repo = repo.scm_instance(cache=False)
62 62
63 63 try:
64 64 # use copy of needed_commits since we modify it while iterating
65 65 for commit_id in list(needed_commits):
66 66 if commit_id.startswith('tag=>'):
67 67 raw_id = commit_id[5:]
68 68 cs_data = {
69 69 'raw_id': commit_id, 'short_id': commit_id,
70 70 'branch': None,
71 71 'git_ref_change': 'tag_add',
72 72 'message': f'Added new tag {raw_id}',
73 73 'author': event.actor.full_contact,
74 74 'date': datetime.datetime.now(),
75 75 'refs': {
76 76 'branches': [],
77 77 'bookmarks': [],
78 78 'tags': []
79 79 }
80 80 }
81 81 commits.append(cs_data)
82 82
83 83 elif commit_id.startswith('delete_branch=>'):
84 84 raw_id = commit_id[15:]
85 85 cs_data = {
86 86 'raw_id': commit_id, 'short_id': commit_id,
87 87 'branch': None,
88 88 'git_ref_change': 'branch_delete',
89 89 'message': f'Deleted branch {raw_id}',
90 90 'author': event.actor.full_contact,
91 91 'date': datetime.datetime.now(),
92 92 'refs': {
93 93 'branches': [],
94 94 'bookmarks': [],
95 95 'tags': []
96 96 }
97 97 }
98 98 commits.append(cs_data)
99 99
100 100 else:
101 101 try:
102 102 cs = vcs_repo.get_commit(commit_id)
103 103 except CommitDoesNotExistError:
104 104 continue # maybe its in next repo
105 105
106 106 cs_data = cs.__json__()
107 107 cs_data['refs'] = cs._get_refs()
108 108
109 109 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
110 110 cs_data['reviewers'] = reviewers
111 111 cs_data['url'] = RepoModel().get_commit_url(
112 112 repo, cs_data['raw_id'], request=event.request)
113 113 cs_data['permalink_url'] = RepoModel().get_commit_url(
114 114 repo, cs_data['raw_id'], request=event.request,
115 115 permalink=True)
116 116 urlified_message, issues_data, errors = process_patterns(
117 117 cs_data['message'], repo.repo_name)
118 118 cs_data['issues'] = issues_data
119 119 cs_data['message_html'] = urlify_commit_message(
120 120 cs_data['message'], repo.repo_name)
121 121 cs_data['message_html_title'] = chop_at_smart(
122 122 cs_data['message'], '\n', suffix_if_chopped='...')
123 123 commits.append(cs_data)
124 124
125 125 needed_commits.remove(commit_id)
126 126
127 127 except Exception:
128 128 log.exception('Failed to extract commits data')
129 129 # we don't send any commits when crash happens, only full list
130 130 # matters we short circuit then.
131 131 return []
132 132
133 # we failed to remove all needed_commits from all repositories
134 if needed_commits:
135 raise ValueError(f'Unexpectedly not found {needed_commits} in all available repos {repos}')
136
133 137 missing_commits = set(commit_ids) - set(c['raw_id'] for c in commits)
134 138 if missing_commits:
135 139 log.error('Inconsistent repository state. '
136 140 'Missing commits: %s', ', '.join(missing_commits))
137 141
138 142 return commits
139 143
140 144
141 145 def _issues_as_dict(commits):
142 146 """ Helper function to serialize issues from commits """
143 147 issues = {}
144 148 for commit in commits:
145 149 for issue in commit['issues']:
146 150 issues[issue['id']] = issue
147 151 return issues
148 152
149 153
150 154 class RepoEvent(RhodeCodeIntegrationEvent):
151 155 """
152 156 Base class for events acting on a repository.
153 157
154 158 :param repo: a :class:`Repository` instance
155 159 """
156 160
157 161 def __init__(self, repo):
158 162 super().__init__()
159 163 self.repo = repo
160 164
161 165 def as_dict(self):
162 166 from rhodecode.model.repo import RepoModel
163 167 data = super().as_dict()
164 168
165 169 extra_fields = collections.OrderedDict()
166 170 for field in self.repo.extra_fields:
167 171 extra_fields[field.field_key] = field.field_value
168 172
169 173 data.update({
170 174 'repo': {
171 175 'repo_id': self.repo.repo_id,
172 176 'repo_name': self.repo.repo_name,
173 177 'repo_type': self.repo.repo_type,
174 178 'url': RepoModel().get_url(
175 179 self.repo, request=self.request),
176 180 'permalink_url': RepoModel().get_url(
177 181 self.repo, request=self.request, permalink=True),
178 182 'extra_fields': extra_fields
179 183 }
180 184 })
181 185 return data
182 186
183 187
184 188 class RepoCommitCommentEvent(RepoEvent):
185 189 """
186 190 An instance of this class is emitted as an :term:`event` after a comment is made
187 191 on repository commit.
188 192 """
189 193
190 194 name = 'repo-commit-comment'
191 195 display_name = lazy_ugettext('repository commit comment')
192 196 description = lazy_ugettext('Event triggered after a comment was made '
193 197 'on commit inside a repository')
194 198
195 199 def __init__(self, repo, commit, comment):
196 200 super().__init__(repo)
197 201 self.commit = commit
198 202 self.comment = comment
199 203
200 204 def as_dict(self):
201 205 data = super().as_dict()
202 206 data['commit'] = {
203 207 'commit_id': self.commit.raw_id,
204 208 'commit_message': self.commit.message,
205 209 'commit_branch': self.commit.branch,
206 210 }
207 211
208 212 data['comment'] = {
209 213 'comment_id': self.comment.comment_id,
210 214 'comment_text': self.comment.text,
211 215 'comment_type': self.comment.comment_type,
212 216 'comment_f_path': self.comment.f_path,
213 217 'comment_line_no': self.comment.line_no,
214 218 'comment_version': self.comment.last_version,
215 219 }
216 220 return data
217 221
218 222
219 223 class RepoCommitCommentEditEvent(RepoEvent):
220 224 """
221 225 An instance of this class is emitted as an :term:`event` after a comment is edited
222 226 on repository commit.
223 227 """
224 228
225 229 name = 'repo-commit-edit-comment'
226 230 display_name = lazy_ugettext('repository commit edit comment')
227 231 description = lazy_ugettext('Event triggered after a comment was edited '
228 232 'on commit inside a repository')
229 233
230 234 def __init__(self, repo, commit, comment):
231 235 super().__init__(repo)
232 236 self.commit = commit
233 237 self.comment = comment
234 238
235 239 def as_dict(self):
236 240 data = super().as_dict()
237 241 data['commit'] = {
238 242 'commit_id': self.commit.raw_id,
239 243 'commit_message': self.commit.message,
240 244 'commit_branch': self.commit.branch,
241 245 }
242 246
243 247 data['comment'] = {
244 248 'comment_id': self.comment.comment_id,
245 249 'comment_text': self.comment.text,
246 250 'comment_type': self.comment.comment_type,
247 251 'comment_f_path': self.comment.f_path,
248 252 'comment_line_no': self.comment.line_no,
249 253 'comment_version': self.comment.last_version,
250 254 }
251 255 return data
252 256
253 257
254 258 class RepoPreCreateEvent(RepoEvent):
255 259 """
256 260 An instance of this class is emitted as an :term:`event` before a repo is
257 261 created.
258 262 """
259 263 name = 'repo-pre-create'
260 264 display_name = lazy_ugettext('repository pre create')
261 265 description = lazy_ugettext('Event triggered before repository is created')
262 266
263 267
264 268 class RepoCreateEvent(RepoEvent):
265 269 """
266 270 An instance of this class is emitted as an :term:`event` whenever a repo is
267 271 created.
268 272 """
269 273 name = 'repo-create'
270 274 display_name = lazy_ugettext('repository created')
271 275 description = lazy_ugettext('Event triggered after repository was created')
272 276
273 277
274 278 class RepoPreDeleteEvent(RepoEvent):
275 279 """
276 280 An instance of this class is emitted as an :term:`event` whenever a repo is
277 281 created.
278 282 """
279 283 name = 'repo-pre-delete'
280 284 display_name = lazy_ugettext('repository pre delete')
281 285 description = lazy_ugettext('Event triggered before a repository is deleted')
282 286
283 287
284 288 class RepoDeleteEvent(RepoEvent):
285 289 """
286 290 An instance of this class is emitted as an :term:`event` whenever a repo is
287 291 created.
288 292 """
289 293 name = 'repo-delete'
290 294 display_name = lazy_ugettext('repository deleted')
291 295 description = lazy_ugettext('Event triggered after repository was deleted')
292 296
293 297
294 298 class RepoVCSEvent(RepoEvent):
295 299 """
296 300 Base class for events triggered by the VCS
297 301 """
298 302 def __init__(self, repo_name, extras):
299 303 self.repo = Repository.get_by_repo_name(repo_name)
300 304 if not self.repo:
301 raise Exception('repo by this name %s does not exist' % repo_name)
305 raise Exception(f'repo by this name {repo_name} does not exist')
302 306 self.extras = extras
303 307 super().__init__(self.repo)
304 308
305 309 @property
306 310 def actor(self):
307 311 if self.extras.get('username'):
308 312 return User.get_by_username(self.extras['username'])
309 313
310 314 @property
311 315 def actor_ip(self):
312 316 if self.extras.get('ip'):
313 317 return self.extras['ip']
314 318
315 319 @property
316 320 def server_url(self):
317 321 if self.extras.get('server_url'):
318 322 return self.extras['server_url']
319 323
320 324 @property
321 325 def request(self):
322 326 return self.extras.get('request') or self.get_request()
323 327
324 328
325 329 class RepoPrePullEvent(RepoVCSEvent):
326 330 """
327 331 An instance of this class is emitted as an :term:`event` before commits
328 332 are pulled from a repo.
329 333 """
330 334 name = 'repo-pre-pull'
331 335 display_name = lazy_ugettext('repository pre pull')
332 336 description = lazy_ugettext('Event triggered before repository code is pulled')
333 337
334 338
335 339 class RepoPullEvent(RepoVCSEvent):
336 340 """
337 341 An instance of this class is emitted as an :term:`event` after commits
338 342 are pulled from a repo.
339 343 """
340 344 name = 'repo-pull'
341 345 display_name = lazy_ugettext('repository pull')
342 346 description = lazy_ugettext('Event triggered after repository code was pulled')
343 347
344 348
345 349 class RepoPrePushEvent(RepoVCSEvent):
346 350 """
347 351 An instance of this class is emitted as an :term:`event` before commits
348 352 are pushed to a repo.
349 353 """
350 354 name = 'repo-pre-push'
351 355 display_name = lazy_ugettext('repository pre push')
352 356 description = lazy_ugettext('Event triggered before the code is '
353 357 'pushed to a repository')
354 358
355 359
356 360 class RepoPushEvent(RepoVCSEvent):
357 361 """
358 362 An instance of this class is emitted as an :term:`event` after commits
359 363 are pushed to a repo.
360 364
361 365 :param extras: (optional) dict of data from proxied VCS actions
362 366 """
363 367 name = 'repo-push'
364 368 display_name = lazy_ugettext('repository push')
365 369 description = lazy_ugettext('Event triggered after the code was '
366 370 'pushed to a repository')
367 371
368 372 def __init__(self, repo_name, pushed_commit_ids, extras):
369 373 super().__init__(repo_name, extras)
370 374 self.pushed_commit_ids = pushed_commit_ids
371 375 self.new_refs = extras.new_refs
372 376
373 377 def as_dict(self):
374 378 data = super().as_dict()
375 379
376 380 def branch_url(branch_name):
377 381 return '{}/changelog?branch={}'.format(
378 382 data['repo']['url'], branch_name)
379 383
380 384 def tag_url(tag_name):
381 385 return '{}/files/{}/'.format(
382 386 data['repo']['url'], tag_name)
383 387
384 388 commits = _commits_as_dict(
385 389 self, commit_ids=self.pushed_commit_ids, repos=[self.repo])
386 390
387 391 last_branch = None
388 392 for commit in reversed(commits):
389 393 commit['branch'] = commit['branch'] or last_branch
390 394 last_branch = commit['branch']
391 395 issues = _issues_as_dict(commits)
392 396
393 397 branches = set()
394 398 tags = set()
395 399 for commit in commits:
396 400 if commit['refs']['tags']:
397 401 for tag in commit['refs']['tags']:
398 402 tags.add(tag)
399 403 if commit['branch']:
400 404 branches.add(commit['branch'])
401 405
402 406 # maybe we have branches in new_refs ?
403 407 try:
404 408 branches = branches.union(set(self.new_refs['branches']))
405 409 except Exception:
406 410 pass
407 411
408 412 branches = [
409 413 {
410 414 'name': branch,
411 415 'url': branch_url(branch)
412 416 }
413 417 for branch in branches
414 418 ]
415 419
416 420 # maybe we have branches in new_refs ?
417 421 try:
418 422 tags = tags.union(set(self.new_refs['tags']))
419 423 except Exception:
420 424 pass
421 425
422 426 tags = [
423 427 {
424 428 'name': tag,
425 429 'url': tag_url(tag)
426 430 }
427 431 for tag in tags
428 432 ]
429 433
430 434 data['push'] = {
431 435 'commits': commits,
432 436 'issues': issues,
433 437 'branches': branches,
434 438 'tags': tags,
435 439 }
436 440 return data
General Comments 0
You need to be logged in to leave comments. Login now