##// END OF EJS Templates
docs: document email integration
dan -
r641:fe7c372b default
parent child Browse files
Show More
@@ -0,0 +1,23 b''
1 .. _integrations-email:
2
3 Email integration
4 =================
5
6 The email integration allows you to send the summary of repo pushes to a
7 list of email recipients in the format:
8
9 An example::
10
11 User: johndoe
12 Branches: default
13 Repository: http://rhodecode.company.com/repo
14 Commit: 8eab60a44a612e331edfcd59b8d96b2f6a935cd9
15 URL: http://rhodecode.company.com/repo/changeset/8eab60a44a612e331edfcd59b8d96b2f6a935cd9
16 Author: John Doe
17 Date: 2016-03-01 11:20:44
18 Commit Message:
19
20 fixed bug with thing
21
22
23 To create one, create a ``email`` integration in `creating-integrations`.
@@ -1,49 +1,50 b''
1 .. _integrations:
1 .. _integrations:
2
2
3 Integrations
3 Integrations
4 ------------
4 ------------
5
5
6 Rhodecode supports integrations with external services for various events
6 Rhodecode 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 (eg. slack) can be added at the same time which is useful for example to post
8 (eg. slack) can be added at the same time which is useful for example to post
9 different events to different slack channels.
9 different events to different slack channels.
10
10
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-slack` |RCCEshort| https://slack.com/
18 :ref:`integrations-hipchat` |RCCEshort| https://www.hipchat.com/
18 :ref:`integrations-hipchat` |RCCEshort| https://www.hipchat.com/
19 :ref:`integrations-webhook` |RCCEshort| POST events as `json` to a custom url
19 :ref:`integrations-webhook` |RCCEshort| POST events as `json` to a custom url
20 :ref:`integrations-email` |RCEEshort| Send repo push commits by email
20 :ref:`integrations-redmine` |RCEEshort| Close/Resolve/Reference redmine issues
21 :ref:`integrations-redmine` |RCEEshort| Close/Resolve/Reference redmine issues
21 :ref:`integrations-jira` |RCEEshort| Close/Resolve/Reference JIRA issues
22 :ref:`integrations-jira` |RCEEshort| Close/Resolve/Reference JIRA issues
22 ============================ ============ =====================================
23 ============================ ============ =====================================
23
24
24 .. _creating-integrations:
25 .. _creating-integrations:
25
26
26 Creating an integration
27 Creating an integration
27 ^^^^^^^^^^^^^^^^^^^^^^^
28 ^^^^^^^^^^^^^^^^^^^^^^^
28
29
29 Integrations can be added globally via the admin UI:
30 Integrations can be added globally via the admin UI:
30
31
31 :menuselection:`Admin --> Integrations`
32 :menuselection:`Admin --> Integrations`
32
33
33 or per repository in the repository settings:
34 or per repository in the repository settings:
34
35
35 :menuselection:`Admin --> Repositories --> Edit --> Integrations`
36 :menuselection:`Admin --> Repositories --> Edit --> Integrations`
36
37
37 To create an integration, select the type from the list of types in the
38 To create an integration, select the type from the list of types in the
38 `Create an integration` section.
39 `Create an integration` section.
39
40
40 The `Current integrations` section shows existing integrations that have been
41 The `Current integrations` section shows existing integrations that have been
41 created along with their type (eg. slack) and enabled status.
42 created along with their type (eg. slack) and enabled status.
42
43
43 .. toctree::
44 .. toctree::
44
45
45 slack
46 slack
46 hipchat
47 hipchat
47 redmine
48 redmine
48 jira
49 jira
49 webhook
50 webhook
@@ -1,284 +1,285 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2016 RhodeCode GmbH
3 # Copyright (C) 2012-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 RhodeCode task modules, containing all task that suppose to be run
22 RhodeCode task modules, containing all task that suppose to be run
23 by celery daemon
23 by celery daemon
24 """
24 """
25
25
26
26
27 import os
27 import os
28 import logging
28 import logging
29
29
30 from celery.task import task
30 from celery.task import task
31 from pylons import config
31 from pylons import config
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib.celerylib import (
34 from rhodecode.lib.celerylib import (
35 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
35 run_task, dbsession, __get_lockkey, LockHeld, DaemonLock,
36 get_session, vcsconnection, RhodecodeCeleryTask)
36 get_session, vcsconnection, RhodecodeCeleryTask)
37 from rhodecode.lib.hooks_base import log_create_repository
37 from rhodecode.lib.hooks_base import log_create_repository
38 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
38 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
39 from rhodecode.lib.utils import add_cache, action_logger
39 from rhodecode.lib.utils import add_cache, action_logger
40 from rhodecode.lib.utils2 import safe_int, str2bool
40 from rhodecode.lib.utils2 import safe_int, str2bool
41 from rhodecode.model.db import Repository, User
41 from rhodecode.model.db import Repository, User
42
42
43
43
44 add_cache(config) # pragma: no cover
44 add_cache(config) # pragma: no cover
45
45
46
46
47 def get_logger(cls):
47 def get_logger(cls):
48 if rhodecode.CELERY_ENABLED:
48 if rhodecode.CELERY_ENABLED:
49 try:
49 try:
50 log = cls.get_logger()
50 log = cls.get_logger()
51 except Exception:
51 except Exception:
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53 else:
53 else:
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56 return log
56 return log
57
57
58
58
59 @task(ignore_result=True, base=RhodecodeCeleryTask)
59 @task(ignore_result=True, base=RhodecodeCeleryTask)
60 @dbsession
60 @dbsession
61 def send_email(recipients, subject, body='', html_body='', email_config=None):
61 def send_email(recipients, subject, body='', html_body='', email_config=None):
62 """
62 """
63 Sends an email with defined parameters from the .ini files.
63 Sends an email with defined parameters from the .ini files.
64
64
65 :param recipients: list of recipients, it this is empty the defined email
65 :param recipients: list of recipients, it this is empty the defined email
66 address from field 'email_to' is used instead
66 address from field 'email_to' is used instead
67 :param subject: subject of the mail
67 :param subject: subject of the mail
68 :param body: body of the mail
68 :param body: body of the mail
69 :param html_body: html version of body
69 :param html_body: html version of body
70 """
70 """
71 log = get_logger(send_email)
71 log = get_logger(send_email)
72
72
73 email_config = email_config or rhodecode.CONFIG
73 email_config = email_config or rhodecode.CONFIG
74 print email_config
74 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
75 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
75 if not recipients:
76 if not recipients:
76 # if recipients are not defined we send to email_config + all admins
77 # if recipients are not defined we send to email_config + all admins
77 admins = [
78 admins = [
78 u.email for u in User.query().filter(User.admin == True).all()]
79 u.email for u in User.query().filter(User.admin == True).all()]
79 recipients = [email_config.get('email_to')] + admins
80 recipients = [email_config.get('email_to')] + admins
80
81
81 mail_server = email_config.get('smtp_server') or None
82 mail_server = email_config.get('smtp_server') or None
82 if mail_server is None:
83 if mail_server is None:
83 log.error("SMTP server information missing. Sending email failed. "
84 log.error("SMTP server information missing. Sending email failed. "
84 "Make sure that `smtp_server` variable is configured "
85 "Make sure that `smtp_server` variable is configured "
85 "inside the .ini file")
86 "inside the .ini file")
86 return False
87 return False
87
88
88 mail_from = email_config.get('app_email_from', 'RhodeCode')
89 mail_from = email_config.get('app_email_from', 'RhodeCode')
89 user = email_config.get('smtp_username')
90 user = email_config.get('smtp_username')
90 passwd = email_config.get('smtp_password')
91 passwd = email_config.get('smtp_password')
91 mail_port = email_config.get('smtp_port')
92 mail_port = email_config.get('smtp_port')
92 tls = str2bool(email_config.get('smtp_use_tls'))
93 tls = str2bool(email_config.get('smtp_use_tls'))
93 ssl = str2bool(email_config.get('smtp_use_ssl'))
94 ssl = str2bool(email_config.get('smtp_use_ssl'))
94 debug = str2bool(email_config.get('debug'))
95 debug = str2bool(email_config.get('debug'))
95 smtp_auth = email_config.get('smtp_auth')
96 smtp_auth = email_config.get('smtp_auth')
96
97
97 try:
98 try:
98 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
99 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
99 mail_port, ssl, tls, debug=debug)
100 mail_port, ssl, tls, debug=debug)
100 m.send(recipients, subject, body, html_body)
101 m.send(recipients, subject, body, html_body)
101 except Exception:
102 except Exception:
102 log.exception('Mail sending failed')
103 log.exception('Mail sending failed')
103 return False
104 return False
104 return True
105 return True
105
106
106
107
107 @task(ignore_result=True, base=RhodecodeCeleryTask)
108 @task(ignore_result=True, base=RhodecodeCeleryTask)
108 @dbsession
109 @dbsession
109 @vcsconnection
110 @vcsconnection
110 def create_repo(form_data, cur_user):
111 def create_repo(form_data, cur_user):
111 from rhodecode.model.repo import RepoModel
112 from rhodecode.model.repo import RepoModel
112 from rhodecode.model.user import UserModel
113 from rhodecode.model.user import UserModel
113 from rhodecode.model.settings import SettingsModel
114 from rhodecode.model.settings import SettingsModel
114
115
115 log = get_logger(create_repo)
116 log = get_logger(create_repo)
116 DBS = get_session()
117 DBS = get_session()
117
118
118 cur_user = UserModel(DBS)._get_user(cur_user)
119 cur_user = UserModel(DBS)._get_user(cur_user)
119 owner = cur_user
120 owner = cur_user
120
121
121 repo_name = form_data['repo_name']
122 repo_name = form_data['repo_name']
122 repo_name_full = form_data['repo_name_full']
123 repo_name_full = form_data['repo_name_full']
123 repo_type = form_data['repo_type']
124 repo_type = form_data['repo_type']
124 description = form_data['repo_description']
125 description = form_data['repo_description']
125 private = form_data['repo_private']
126 private = form_data['repo_private']
126 clone_uri = form_data.get('clone_uri')
127 clone_uri = form_data.get('clone_uri')
127 repo_group = safe_int(form_data['repo_group'])
128 repo_group = safe_int(form_data['repo_group'])
128 landing_rev = form_data['repo_landing_rev']
129 landing_rev = form_data['repo_landing_rev']
129 copy_fork_permissions = form_data.get('copy_permissions')
130 copy_fork_permissions = form_data.get('copy_permissions')
130 copy_group_permissions = form_data.get('repo_copy_permissions')
131 copy_group_permissions = form_data.get('repo_copy_permissions')
131 fork_of = form_data.get('fork_parent_id')
132 fork_of = form_data.get('fork_parent_id')
132 state = form_data.get('repo_state', Repository.STATE_PENDING)
133 state = form_data.get('repo_state', Repository.STATE_PENDING)
133
134
134 # repo creation defaults, private and repo_type are filled in form
135 # repo creation defaults, private and repo_type are filled in form
135 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
136 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
136 enable_statistics = form_data.get(
137 enable_statistics = form_data.get(
137 'enable_statistics', defs.get('repo_enable_statistics'))
138 'enable_statistics', defs.get('repo_enable_statistics'))
138 enable_locking = form_data.get(
139 enable_locking = form_data.get(
139 'enable_locking', defs.get('repo_enable_locking'))
140 'enable_locking', defs.get('repo_enable_locking'))
140 enable_downloads = form_data.get(
141 enable_downloads = form_data.get(
141 'enable_downloads', defs.get('repo_enable_downloads'))
142 'enable_downloads', defs.get('repo_enable_downloads'))
142
143
143 try:
144 try:
144 RepoModel(DBS)._create_repo(
145 RepoModel(DBS)._create_repo(
145 repo_name=repo_name_full,
146 repo_name=repo_name_full,
146 repo_type=repo_type,
147 repo_type=repo_type,
147 description=description,
148 description=description,
148 owner=owner,
149 owner=owner,
149 private=private,
150 private=private,
150 clone_uri=clone_uri,
151 clone_uri=clone_uri,
151 repo_group=repo_group,
152 repo_group=repo_group,
152 landing_rev=landing_rev,
153 landing_rev=landing_rev,
153 fork_of=fork_of,
154 fork_of=fork_of,
154 copy_fork_permissions=copy_fork_permissions,
155 copy_fork_permissions=copy_fork_permissions,
155 copy_group_permissions=copy_group_permissions,
156 copy_group_permissions=copy_group_permissions,
156 enable_statistics=enable_statistics,
157 enable_statistics=enable_statistics,
157 enable_locking=enable_locking,
158 enable_locking=enable_locking,
158 enable_downloads=enable_downloads,
159 enable_downloads=enable_downloads,
159 state=state
160 state=state
160 )
161 )
161
162
162 action_logger(cur_user, 'user_created_repo',
163 action_logger(cur_user, 'user_created_repo',
163 repo_name_full, '', DBS)
164 repo_name_full, '', DBS)
164 DBS.commit()
165 DBS.commit()
165
166
166 # now create this repo on Filesystem
167 # now create this repo on Filesystem
167 RepoModel(DBS)._create_filesystem_repo(
168 RepoModel(DBS)._create_filesystem_repo(
168 repo_name=repo_name,
169 repo_name=repo_name,
169 repo_type=repo_type,
170 repo_type=repo_type,
170 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
171 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
171 clone_uri=clone_uri,
172 clone_uri=clone_uri,
172 )
173 )
173 repo = Repository.get_by_repo_name(repo_name_full)
174 repo = Repository.get_by_repo_name(repo_name_full)
174 log_create_repository(created_by=owner.username, **repo.get_dict())
175 log_create_repository(created_by=owner.username, **repo.get_dict())
175
176
176 # update repo commit caches initially
177 # update repo commit caches initially
177 repo.update_commit_cache()
178 repo.update_commit_cache()
178
179
179 # set new created state
180 # set new created state
180 repo.set_state(Repository.STATE_CREATED)
181 repo.set_state(Repository.STATE_CREATED)
181 DBS.commit()
182 DBS.commit()
182 except Exception as e:
183 except Exception as e:
183 log.warning('Exception %s occurred when creating repository, '
184 log.warning('Exception %s occurred when creating repository, '
184 'doing cleanup...', e)
185 'doing cleanup...', e)
185 # rollback things manually !
186 # rollback things manually !
186 repo = Repository.get_by_repo_name(repo_name_full)
187 repo = Repository.get_by_repo_name(repo_name_full)
187 if repo:
188 if repo:
188 Repository.delete(repo.repo_id)
189 Repository.delete(repo.repo_id)
189 DBS.commit()
190 DBS.commit()
190 RepoModel(DBS)._delete_filesystem_repo(repo)
191 RepoModel(DBS)._delete_filesystem_repo(repo)
191 raise
192 raise
192
193
193 # it's an odd fix to make celery fail task when exception occurs
194 # it's an odd fix to make celery fail task when exception occurs
194 def on_failure(self, *args, **kwargs):
195 def on_failure(self, *args, **kwargs):
195 pass
196 pass
196
197
197 return True
198 return True
198
199
199
200
200 @task(ignore_result=True, base=RhodecodeCeleryTask)
201 @task(ignore_result=True, base=RhodecodeCeleryTask)
201 @dbsession
202 @dbsession
202 @vcsconnection
203 @vcsconnection
203 def create_repo_fork(form_data, cur_user):
204 def create_repo_fork(form_data, cur_user):
204 """
205 """
205 Creates a fork of repository using internal VCS methods
206 Creates a fork of repository using internal VCS methods
206
207
207 :param form_data:
208 :param form_data:
208 :param cur_user:
209 :param cur_user:
209 """
210 """
210 from rhodecode.model.repo import RepoModel
211 from rhodecode.model.repo import RepoModel
211 from rhodecode.model.user import UserModel
212 from rhodecode.model.user import UserModel
212
213
213 log = get_logger(create_repo_fork)
214 log = get_logger(create_repo_fork)
214 DBS = get_session()
215 DBS = get_session()
215
216
216 cur_user = UserModel(DBS)._get_user(cur_user)
217 cur_user = UserModel(DBS)._get_user(cur_user)
217 owner = cur_user
218 owner = cur_user
218
219
219 repo_name = form_data['repo_name'] # fork in this case
220 repo_name = form_data['repo_name'] # fork in this case
220 repo_name_full = form_data['repo_name_full']
221 repo_name_full = form_data['repo_name_full']
221 repo_type = form_data['repo_type']
222 repo_type = form_data['repo_type']
222 description = form_data['description']
223 description = form_data['description']
223 private = form_data['private']
224 private = form_data['private']
224 clone_uri = form_data.get('clone_uri')
225 clone_uri = form_data.get('clone_uri')
225 repo_group = safe_int(form_data['repo_group'])
226 repo_group = safe_int(form_data['repo_group'])
226 landing_rev = form_data['landing_rev']
227 landing_rev = form_data['landing_rev']
227 copy_fork_permissions = form_data.get('copy_permissions')
228 copy_fork_permissions = form_data.get('copy_permissions')
228 fork_id = safe_int(form_data.get('fork_parent_id'))
229 fork_id = safe_int(form_data.get('fork_parent_id'))
229
230
230 try:
231 try:
231 fork_of = RepoModel(DBS)._get_repo(fork_id)
232 fork_of = RepoModel(DBS)._get_repo(fork_id)
232 RepoModel(DBS)._create_repo(
233 RepoModel(DBS)._create_repo(
233 repo_name=repo_name_full,
234 repo_name=repo_name_full,
234 repo_type=repo_type,
235 repo_type=repo_type,
235 description=description,
236 description=description,
236 owner=owner,
237 owner=owner,
237 private=private,
238 private=private,
238 clone_uri=clone_uri,
239 clone_uri=clone_uri,
239 repo_group=repo_group,
240 repo_group=repo_group,
240 landing_rev=landing_rev,
241 landing_rev=landing_rev,
241 fork_of=fork_of,
242 fork_of=fork_of,
242 copy_fork_permissions=copy_fork_permissions
243 copy_fork_permissions=copy_fork_permissions
243 )
244 )
244 action_logger(cur_user, 'user_forked_repo:%s' % repo_name_full,
245 action_logger(cur_user, 'user_forked_repo:%s' % repo_name_full,
245 fork_of.repo_name, '', DBS)
246 fork_of.repo_name, '', DBS)
246 DBS.commit()
247 DBS.commit()
247
248
248 base_path = Repository.base_path()
249 base_path = Repository.base_path()
249 source_repo_path = os.path.join(base_path, fork_of.repo_name)
250 source_repo_path = os.path.join(base_path, fork_of.repo_name)
250
251
251 # now create this repo on Filesystem
252 # now create this repo on Filesystem
252 RepoModel(DBS)._create_filesystem_repo(
253 RepoModel(DBS)._create_filesystem_repo(
253 repo_name=repo_name,
254 repo_name=repo_name,
254 repo_type=repo_type,
255 repo_type=repo_type,
255 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
256 repo_group=RepoModel(DBS)._get_repo_group(repo_group),
256 clone_uri=source_repo_path,
257 clone_uri=source_repo_path,
257 )
258 )
258 repo = Repository.get_by_repo_name(repo_name_full)
259 repo = Repository.get_by_repo_name(repo_name_full)
259 log_create_repository(created_by=owner.username, **repo.get_dict())
260 log_create_repository(created_by=owner.username, **repo.get_dict())
260
261
261 # update repo commit caches initially
262 # update repo commit caches initially
262 config = repo._config
263 config = repo._config
263 config.set('extensions', 'largefiles', '')
264 config.set('extensions', 'largefiles', '')
264 repo.update_commit_cache(config=config)
265 repo.update_commit_cache(config=config)
265
266
266 # set new created state
267 # set new created state
267 repo.set_state(Repository.STATE_CREATED)
268 repo.set_state(Repository.STATE_CREATED)
268 DBS.commit()
269 DBS.commit()
269 except Exception as e:
270 except Exception as e:
270 log.warning('Exception %s occurred when forking repository, '
271 log.warning('Exception %s occurred when forking repository, '
271 'doing cleanup...', e)
272 'doing cleanup...', e)
272 # rollback things manually !
273 # rollback things manually !
273 repo = Repository.get_by_repo_name(repo_name_full)
274 repo = Repository.get_by_repo_name(repo_name_full)
274 if repo:
275 if repo:
275 Repository.delete(repo.repo_id)
276 Repository.delete(repo.repo_id)
276 DBS.commit()
277 DBS.commit()
277 RepoModel(DBS)._delete_filesystem_repo(repo)
278 RepoModel(DBS)._delete_filesystem_repo(repo)
278 raise
279 raise
279
280
280 # it's an odd fix to make celery fail task when exception occurs
281 # it's an odd fix to make celery fail task when exception occurs
281 def on_failure(self, *args, **kwargs):
282 def on_failure(self, *args, **kwargs):
282 pass
283 pass
283
284
284 return True
285 return True
General Comments 0
You need to be logged in to leave comments. Login now