##// END OF EJS Templates
fix(mailing): don't default to empty string for smtp_password and user, since mailing lib only expects None as empty values
super-admin -
r5488:186b51dd default
parent child Browse files
Show More
@@ -1,454 +1,455 b''
1 # Copyright (C) 2012-2023 RhodeCode GmbH
1 # Copyright (C) 2012-2024 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 """
20 20 RhodeCode task modules, containing all task that suppose to be run
21 21 by celery daemon
22 22 """
23 23
24 24 import os
25 25 import time
26 26
27 27 from pyramid_mailer.mailer import Mailer
28 28 from pyramid_mailer.message import Message
29 29 from email.utils import formatdate
30 30
31 31 import rhodecode
32 32 from rhodecode.lib import audit_logger
33 33 from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask, run_task
34 34 from rhodecode.lib import hooks_base
35 35 from rhodecode.lib.utils import adopt_for_celery
36 36 from rhodecode.lib.utils2 import safe_int, str2bool, aslist
37 37 from rhodecode.lib.statsd_client import StatsdClient
38 38 from rhodecode.model.db import (
39 39 true, null, Session, IntegrityError, Repository, RepoGroup, User)
40 40 from rhodecode.model.permission import PermissionModel
41 41
42 42
43 43 @async_task(ignore_result=True, base=RequestContextTask)
44 44 def send_email(recipients, subject, body='', html_body='', email_config=None,
45 45 extra_headers=None):
46 46 """
47 47 Sends an email with defined parameters from the .ini files.
48 48
49 49 :param recipients: list of recipients, it this is empty the defined email
50 50 address from field 'email_to' is used instead
51 51 :param subject: subject of the mail
52 52 :param body: body of the mail
53 53 :param html_body: html version of body
54 54 :param email_config: specify custom configuration for mailer
55 55 :param extra_headers: specify custom headers
56 56 """
57 57 log = get_logger(send_email)
58 58
59 59 email_config = email_config or rhodecode.CONFIG
60 60
61 61 mail_server = email_config.get('smtp_server') or None
62 62 if mail_server is None:
63 63 log.error("SMTP server information missing. Sending email failed. "
64 64 "Make sure that `smtp_server` variable is configured "
65 65 "inside the .ini file")
66 66 return False
67
68 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
67 conf_prefix = email_config.get('email_prefix', None)
68 prefix = f'{conf_prefix} ' if conf_prefix else ''
69 subject = f"{prefix}{subject}"
69 70
70 71 if recipients:
71 72 if isinstance(recipients, str):
72 73 recipients = recipients.split(',')
73 74 else:
74 75 # if recipients are not defined we send to email_config + all admins
75 76 admins = []
76 77 for u in User.query().filter(User.admin == true()).all():
77 78 if u.email:
78 79 admins.append(u.email)
79 80 recipients = []
80 81 config_email = email_config.get('email_to')
81 82 if config_email:
82 83 recipients += [config_email]
83 84 recipients += admins
84 85
85 86 # translate our LEGACY config into the one that pyramid_mailer supports
86 87 email_conf = dict(
87 88 host=mail_server,
88 89 port=email_config.get('smtp_port', 25),
89 username=email_config.get('smtp_username'),
90 password=email_config.get('smtp_password'),
90 username=email_config.get('smtp_username', None),
91 password=email_config.get('smtp_password', None),
91 92
92 93 tls=str2bool(email_config.get('smtp_use_tls')),
93 94 ssl=str2bool(email_config.get('smtp_use_ssl')),
94 95
95 96 # SSL key file
96 97 # keyfile='',
97 98
98 99 # SSL certificate file
99 100 # certfile='',
100 101
101 102 # Location of maildir
102 103 # queue_path='',
103 104
104 105 default_sender=email_config.get('app_email_from', 'RhodeCode-noreply@rhodecode.com'),
105 106
106 107 debug=str2bool(email_config.get('smtp_debug')),
107 108 # /usr/sbin/sendmail Sendmail executable
108 109 # sendmail_app='',
109 110
110 111 # {sendmail_app} -t -i -f {sender} Template for sendmail execution
111 112 # sendmail_template='',
112 113 )
113 114
114 115 if extra_headers is None:
115 116 extra_headers = {}
116 117
117 118 extra_headers.setdefault('Date', formatdate(time.time()))
118 119
119 120 if 'thread_ids' in extra_headers:
120 121 thread_ids = extra_headers.pop('thread_ids')
121 122 extra_headers['References'] = ' '.join('<{}>'.format(t) for t in thread_ids)
122 123
123 124 try:
124 125 mailer = Mailer(**email_conf)
125 126
126 127 message = Message(subject=subject,
127 128 sender=email_conf['default_sender'],
128 129 recipients=recipients,
129 130 body=body, html=html_body,
130 131 extra_headers=extra_headers)
131 132 mailer.send_immediately(message)
132 133 statsd = StatsdClient.statsd
133 134 if statsd:
134 135 statsd.incr('rhodecode_email_sent_total')
135 136
136 137 except Exception:
137 138 log.exception('Mail sending failed')
138 139 return False
139 140 return True
140 141
141 142
142 143 @async_task(ignore_result=True, base=RequestContextTask)
143 144 def create_repo(form_data, cur_user):
144 145 from rhodecode.model.repo import RepoModel
145 146 from rhodecode.model.user import UserModel
146 147 from rhodecode.model.scm import ScmModel
147 148 from rhodecode.model.settings import SettingsModel
148 149
149 150 log = get_logger(create_repo)
150 151
151 152 cur_user = UserModel()._get_user(cur_user)
152 153 owner = cur_user
153 154
154 155 repo_name = form_data['repo_name']
155 156 repo_name_full = form_data['repo_name_full']
156 157 repo_type = form_data['repo_type']
157 158 description = form_data['repo_description']
158 159 private = form_data['repo_private']
159 160 clone_uri = form_data.get('clone_uri')
160 161 repo_group = safe_int(form_data['repo_group'])
161 162 copy_fork_permissions = form_data.get('copy_permissions')
162 163 copy_group_permissions = form_data.get('repo_copy_permissions')
163 164 fork_of = form_data.get('fork_parent_id')
164 165 state = form_data.get('repo_state', Repository.STATE_PENDING)
165 166
166 167 # repo creation defaults, private and repo_type are filled in form
167 168 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
168 169 enable_statistics = form_data.get(
169 170 'enable_statistics', defs.get('repo_enable_statistics'))
170 171 enable_locking = form_data.get(
171 172 'enable_locking', defs.get('repo_enable_locking'))
172 173 enable_downloads = form_data.get(
173 174 'enable_downloads', defs.get('repo_enable_downloads'))
174 175
175 176 # set landing rev based on default branches for SCM
176 177 landing_ref, _label = ScmModel.backend_landing_ref(repo_type)
177 178
178 179 try:
179 180 RepoModel()._create_repo(
180 181 repo_name=repo_name_full,
181 182 repo_type=repo_type,
182 183 description=description,
183 184 owner=owner,
184 185 private=private,
185 186 clone_uri=clone_uri,
186 187 repo_group=repo_group,
187 188 landing_rev=landing_ref,
188 189 fork_of=fork_of,
189 190 copy_fork_permissions=copy_fork_permissions,
190 191 copy_group_permissions=copy_group_permissions,
191 192 enable_statistics=enable_statistics,
192 193 enable_locking=enable_locking,
193 194 enable_downloads=enable_downloads,
194 195 state=state
195 196 )
196 197
197 198 Session().commit()
198 199
199 200 # now create this repo on Filesystem
200 201 RepoModel()._create_filesystem_repo(
201 202 repo_name=repo_name,
202 203 repo_type=repo_type,
203 204 repo_group=RepoModel()._get_repo_group(repo_group),
204 205 clone_uri=clone_uri,
205 206 )
206 207 repo = Repository.get_by_repo_name(repo_name_full)
207 208 hooks_base.create_repository(created_by=owner.username, **repo.get_dict())
208 209
209 210 # update repo commit caches initially
210 211 repo.update_commit_cache(recursive=False)
211 212
212 213 # set new created state
213 214 repo.set_state(Repository.STATE_CREATED)
214 215 repo_id = repo.repo_id
215 216 repo_data = repo.get_api_data()
216 217
217 218 audit_logger.store(
218 219 'repo.create', action_data={'data': repo_data},
219 220 user=cur_user,
220 221 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
221 222
222 223 Session().commit()
223 224
224 225 PermissionModel().trigger_permission_flush()
225 226
226 227 except Exception as e:
227 228 log.warning('Exception occurred when creating repository, '
228 229 'doing cleanup...', exc_info=True)
229 230 if isinstance(e, IntegrityError):
230 231 Session().rollback()
231 232
232 233 # rollback things manually !
233 234 repo = Repository.get_by_repo_name(repo_name_full)
234 235 if repo:
235 236 Repository.delete(repo.repo_id)
236 237 Session().commit()
237 238 RepoModel()._delete_filesystem_repo(repo)
238 239 log.info('Cleanup of repo %s finished', repo_name_full)
239 240 raise
240 241
241 242 return True
242 243
243 244
244 245 @async_task(ignore_result=True, base=RequestContextTask)
245 246 def create_repo_fork(form_data, cur_user):
246 247 """
247 248 Creates a fork of repository using internal VCS methods
248 249 """
249 250 from rhodecode.model.repo import RepoModel
250 251 from rhodecode.model.user import UserModel
251 252
252 253 log = get_logger(create_repo_fork)
253 254
254 255 cur_user = UserModel()._get_user(cur_user)
255 256 owner = cur_user
256 257
257 258 repo_name = form_data['repo_name'] # fork in this case
258 259 repo_name_full = form_data['repo_name_full']
259 260 repo_type = form_data['repo_type']
260 261 description = form_data['description']
261 262 private = form_data['private']
262 263 clone_uri = form_data.get('clone_uri')
263 264 repo_group = safe_int(form_data['repo_group'])
264 265 landing_ref = form_data['landing_rev']
265 266 copy_fork_permissions = form_data.get('copy_permissions')
266 267 fork_id = safe_int(form_data.get('fork_parent_id'))
267 268
268 269 try:
269 270 fork_of = RepoModel()._get_repo(fork_id)
270 271 RepoModel()._create_repo(
271 272 repo_name=repo_name_full,
272 273 repo_type=repo_type,
273 274 description=description,
274 275 owner=owner,
275 276 private=private,
276 277 clone_uri=clone_uri,
277 278 repo_group=repo_group,
278 279 landing_rev=landing_ref,
279 280 fork_of=fork_of,
280 281 copy_fork_permissions=copy_fork_permissions
281 282 )
282 283
283 284 Session().commit()
284 285
285 286 base_path = Repository.base_path()
286 287 source_repo_path = os.path.join(base_path, fork_of.repo_name)
287 288
288 289 # now create this repo on Filesystem
289 290 RepoModel()._create_filesystem_repo(
290 291 repo_name=repo_name,
291 292 repo_type=repo_type,
292 293 repo_group=RepoModel()._get_repo_group(repo_group),
293 294 clone_uri=source_repo_path,
294 295 )
295 296 repo = Repository.get_by_repo_name(repo_name_full)
296 297 hooks_base.create_repository(created_by=owner.username, **repo.get_dict())
297 298
298 299 # update repo commit caches initially
299 300 config = repo._config
300 301 config.set('extensions', 'largefiles', '')
301 302 repo.update_commit_cache(config=config, recursive=False)
302 303
303 304 # set new created state
304 305 repo.set_state(Repository.STATE_CREATED)
305 306
306 307 repo_id = repo.repo_id
307 308 repo_data = repo.get_api_data()
308 309 audit_logger.store(
309 310 'repo.fork', action_data={'data': repo_data},
310 311 user=cur_user,
311 312 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
312 313
313 314 Session().commit()
314 315 except Exception as e:
315 316 log.warning('Exception occurred when forking repository, '
316 317 'doing cleanup...', exc_info=True)
317 318 if isinstance(e, IntegrityError):
318 319 Session().rollback()
319 320
320 321 # rollback things manually !
321 322 repo = Repository.get_by_repo_name(repo_name_full)
322 323 if repo:
323 324 Repository.delete(repo.repo_id)
324 325 Session().commit()
325 326 RepoModel()._delete_filesystem_repo(repo)
326 327 log.info('Cleanup of repo %s finished', repo_name_full)
327 328 raise
328 329
329 330 return True
330 331
331 332
332 333 @async_task(ignore_result=True, base=RequestContextTask)
333 334 def repo_maintenance(repoid):
334 335 from rhodecode.lib import repo_maintenance as repo_maintenance_lib
335 336 log = get_logger(repo_maintenance)
336 337 repo = Repository.get_by_id_or_repo_name(repoid)
337 338 if repo:
338 339 maintenance = repo_maintenance_lib.RepoMaintenance()
339 340 tasks = maintenance.get_tasks_for_repo(repo)
340 341 log.debug('Executing %s tasks on repo `%s`', tasks, repoid)
341 342 executed_types = maintenance.execute(repo)
342 343 log.debug('Got execution results %s', executed_types)
343 344 else:
344 345 log.debug('Repo `%s` not found or without a clone_url', repoid)
345 346
346 347
347 348 @async_task(ignore_result=True, base=RequestContextTask)
348 349 def check_for_update(send_email_notification=True, email_recipients=None):
349 350 from rhodecode.model.update import UpdateModel
350 351 from rhodecode.model.notification import EmailNotificationModel
351 352
352 353 log = get_logger(check_for_update)
353 354 update_url = UpdateModel().get_update_url()
354 355 cur_ver = rhodecode.__version__
355 356
356 357 try:
357 358 data = UpdateModel().get_update_data(update_url)
358 359
359 360 current_ver = UpdateModel().get_stored_version(fallback=cur_ver)
360 361 latest_ver = data['versions'][0]['version']
361 362 UpdateModel().store_version(latest_ver)
362 363
363 364 if send_email_notification:
364 365 log.debug('Send email notification is enabled. '
365 366 'Current RhodeCode version: %s, latest known: %s', current_ver, latest_ver)
366 367 if UpdateModel().is_outdated(current_ver, latest_ver):
367 368
368 369 email_kwargs = {
369 370 'current_ver': current_ver,
370 371 'latest_ver': latest_ver,
371 372 }
372 373
373 374 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
374 375 EmailNotificationModel.TYPE_UPDATE_AVAILABLE, **email_kwargs)
375 376
376 377 email_recipients = aslist(email_recipients, sep=',') or \
377 378 [user.email for user in User.get_all_super_admins()]
378 379 run_task(send_email, email_recipients, subject,
379 380 email_body_plaintext, email_body)
380 381
381 382 except Exception:
382 383 log.exception('Failed to check for update')
383 384 raise
384 385
385 386
386 387 def sync_last_update_for_objects(*args, **kwargs):
387 388 skip_repos = kwargs.get('skip_repos')
388 389 if not skip_repos:
389 390 repos = Repository.query() \
390 391 .order_by(Repository.group_id.asc())
391 392
392 393 for repo in repos:
393 394 repo.update_commit_cache(recursive=False)
394 395
395 396 skip_groups = kwargs.get('skip_groups')
396 397 if not skip_groups:
397 398 repo_groups = RepoGroup.query() \
398 399 .filter(RepoGroup.group_parent_id == null())
399 400
400 401 for root_gr in repo_groups:
401 402 for repo_gr in reversed(root_gr.recursive_groups()):
402 403 repo_gr.update_commit_cache()
403 404
404 405
405 406 @async_task(ignore_result=True, base=RequestContextTask)
406 407 def test_celery_exception(msg):
407 408 raise Exception(f'Test exception: {msg}')
408 409
409 410
410 411 @async_task(ignore_result=True, base=RequestContextTask)
411 412 def sync_last_update(*args, **kwargs):
412 413 sync_last_update_for_objects(*args, **kwargs)
413 414
414 415
415 416 @async_task(ignore_result=False)
416 417 def beat_check(*args, **kwargs):
417 418 log = get_logger(beat_check)
418 419 log.info('%r: Got args: %r and kwargs %r', beat_check, args, kwargs)
419 420 return time.time()
420 421
421 422
422 423 @async_task
423 424 @adopt_for_celery
424 425 def repo_size(extras):
425 426 from rhodecode.lib.hooks_base import repo_size
426 427 return repo_size(extras)
427 428
428 429
429 430 @async_task
430 431 @adopt_for_celery
431 432 def pre_pull(extras):
432 433 from rhodecode.lib.hooks_base import pre_pull
433 434 return pre_pull(extras)
434 435
435 436
436 437 @async_task
437 438 @adopt_for_celery
438 439 def post_pull(extras):
439 440 from rhodecode.lib.hooks_base import post_pull
440 441 return post_pull(extras)
441 442
442 443
443 444 @async_task
444 445 @adopt_for_celery
445 446 def pre_push(extras):
446 447 from rhodecode.lib.hooks_base import pre_push
447 448 return pre_push(extras)
448 449
449 450
450 451 @async_task
451 452 @adopt_for_celery
452 453 def post_push(extras):
453 454 from rhodecode.lib.hooks_base import post_push
454 455 return post_push(extras)
General Comments 0
You need to be logged in to leave comments. Login now