##// END OF EJS Templates
update: add new async task to check for updates via scheduler....
marcink -
r2431:61a0f874 default
parent child Browse files
Show More
@@ -0,0 +1,71 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2013-2017 RhodeCode GmbH
4 #
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
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
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/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22 import urllib2
23 from packaging import version
24
25 import rhodecode
26 from rhodecode.lib.ext_json import json
27 from rhodecode.model import BaseModel
28 from rhodecode.model.meta import Session
29 from rhodecode.model.settings import SettingsModel
30
31
32 log = logging.getLogger(__name__)
33
34
35 class UpdateModel(BaseModel):
36 UPDATE_SETTINGS_KEY = 'update_version'
37 UPDATE_URL_SETTINGS_KEY = 'rhodecode_update_url'
38
39 @staticmethod
40 def get_update_data(update_url):
41 """Return the JSON update data."""
42 ver = rhodecode.__version__
43 log.debug('Checking for upgrade on `%s` server', update_url)
44 opener = urllib2.build_opener()
45 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
46 response = opener.open(update_url)
47 response_data = response.read()
48 data = json.loads(response_data)
49 log.debug('update server returned data')
50 return data
51
52 def get_update_url(self):
53 settings = SettingsModel().get_all_settings()
54 return settings.get(self.UPDATE_URL_SETTINGS_KEY)
55
56 def store_version(self, version):
57 log.debug('Storing version %s into settings', version)
58 setting = SettingsModel().create_or_update_setting(
59 self.UPDATE_SETTINGS_KEY, version)
60 Session().add(setting)
61 Session().commit()
62
63 def get_stored_version(self):
64 obj = SettingsModel().get_setting_by_name(self.UPDATE_SETTINGS_KEY)
65 if obj:
66 return obj.app_settings_value
67 return '0.0.0'
68
69 def is_outdated(self, cur_version, latest_version=None):
70 latest_version = latest_version or self.get_stored_version()
71 return version.Version(latest_version) > version.Version(cur_version)
@@ -1,730 +1,729 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 import mock
21 import mock
22 import pytest
22 import pytest
23
23
24 import rhodecode
24 import rhodecode
25 from rhodecode.apps._base import ADMIN_PREFIX
25 from rhodecode.apps._base import ADMIN_PREFIX
26 from rhodecode.lib.utils2 import md5
26 from rhodecode.lib.utils2 import md5
27 from rhodecode.model.db import RhodeCodeUi
27 from rhodecode.model.db import RhodeCodeUi
28 from rhodecode.model.meta import Session
28 from rhodecode.model.meta import Session
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
29 from rhodecode.model.settings import SettingsModel, IssueTrackerSettingsModel
30 from rhodecode.tests import assert_session_flash
30 from rhodecode.tests import assert_session_flash
31 from rhodecode.tests.utils import AssertResponse
31 from rhodecode.tests.utils import AssertResponse
32
32
33
33
34 UPDATE_DATA_QUALNAME = (
34 UPDATE_DATA_QUALNAME = 'rhodecode.model.update.UpdateModel.get_update_data'
35 'rhodecode.apps.admin.views.system_info.AdminSystemInfoSettingsView.get_update_data')
36
35
37
36
38 def route_path(name, params=None, **kwargs):
37 def route_path(name, params=None, **kwargs):
39 import urllib
38 import urllib
40 from rhodecode.apps._base import ADMIN_PREFIX
39 from rhodecode.apps._base import ADMIN_PREFIX
41
40
42 base_url = {
41 base_url = {
43
42
44 'admin_settings':
43 'admin_settings':
45 ADMIN_PREFIX +'/settings',
44 ADMIN_PREFIX +'/settings',
46 'admin_settings_update':
45 'admin_settings_update':
47 ADMIN_PREFIX + '/settings/update',
46 ADMIN_PREFIX + '/settings/update',
48 'admin_settings_global':
47 'admin_settings_global':
49 ADMIN_PREFIX + '/settings/global',
48 ADMIN_PREFIX + '/settings/global',
50 'admin_settings_global_update':
49 'admin_settings_global_update':
51 ADMIN_PREFIX + '/settings/global/update',
50 ADMIN_PREFIX + '/settings/global/update',
52 'admin_settings_vcs':
51 'admin_settings_vcs':
53 ADMIN_PREFIX + '/settings/vcs',
52 ADMIN_PREFIX + '/settings/vcs',
54 'admin_settings_vcs_update':
53 'admin_settings_vcs_update':
55 ADMIN_PREFIX + '/settings/vcs/update',
54 ADMIN_PREFIX + '/settings/vcs/update',
56 'admin_settings_vcs_svn_pattern_delete':
55 'admin_settings_vcs_svn_pattern_delete':
57 ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete',
56 ADMIN_PREFIX + '/settings/vcs/svn_pattern_delete',
58 'admin_settings_mapping':
57 'admin_settings_mapping':
59 ADMIN_PREFIX + '/settings/mapping',
58 ADMIN_PREFIX + '/settings/mapping',
60 'admin_settings_mapping_update':
59 'admin_settings_mapping_update':
61 ADMIN_PREFIX + '/settings/mapping/update',
60 ADMIN_PREFIX + '/settings/mapping/update',
62 'admin_settings_visual':
61 'admin_settings_visual':
63 ADMIN_PREFIX + '/settings/visual',
62 ADMIN_PREFIX + '/settings/visual',
64 'admin_settings_visual_update':
63 'admin_settings_visual_update':
65 ADMIN_PREFIX + '/settings/visual/update',
64 ADMIN_PREFIX + '/settings/visual/update',
66 'admin_settings_issuetracker':
65 'admin_settings_issuetracker':
67 ADMIN_PREFIX + '/settings/issue-tracker',
66 ADMIN_PREFIX + '/settings/issue-tracker',
68 'admin_settings_issuetracker_update':
67 'admin_settings_issuetracker_update':
69 ADMIN_PREFIX + '/settings/issue-tracker/update',
68 ADMIN_PREFIX + '/settings/issue-tracker/update',
70 'admin_settings_issuetracker_test':
69 'admin_settings_issuetracker_test':
71 ADMIN_PREFIX + '/settings/issue-tracker/test',
70 ADMIN_PREFIX + '/settings/issue-tracker/test',
72 'admin_settings_issuetracker_delete':
71 'admin_settings_issuetracker_delete':
73 ADMIN_PREFIX + '/settings/issue-tracker/delete',
72 ADMIN_PREFIX + '/settings/issue-tracker/delete',
74 'admin_settings_email':
73 'admin_settings_email':
75 ADMIN_PREFIX + '/settings/email',
74 ADMIN_PREFIX + '/settings/email',
76 'admin_settings_email_update':
75 'admin_settings_email_update':
77 ADMIN_PREFIX + '/settings/email/update',
76 ADMIN_PREFIX + '/settings/email/update',
78 'admin_settings_hooks':
77 'admin_settings_hooks':
79 ADMIN_PREFIX + '/settings/hooks',
78 ADMIN_PREFIX + '/settings/hooks',
80 'admin_settings_hooks_update':
79 'admin_settings_hooks_update':
81 ADMIN_PREFIX + '/settings/hooks/update',
80 ADMIN_PREFIX + '/settings/hooks/update',
82 'admin_settings_hooks_delete':
81 'admin_settings_hooks_delete':
83 ADMIN_PREFIX + '/settings/hooks/delete',
82 ADMIN_PREFIX + '/settings/hooks/delete',
84 'admin_settings_search':
83 'admin_settings_search':
85 ADMIN_PREFIX + '/settings/search',
84 ADMIN_PREFIX + '/settings/search',
86 'admin_settings_labs':
85 'admin_settings_labs':
87 ADMIN_PREFIX + '/settings/labs',
86 ADMIN_PREFIX + '/settings/labs',
88 'admin_settings_labs_update':
87 'admin_settings_labs_update':
89 ADMIN_PREFIX + '/settings/labs/update',
88 ADMIN_PREFIX + '/settings/labs/update',
90
89
91 'admin_settings_sessions':
90 'admin_settings_sessions':
92 ADMIN_PREFIX + '/settings/sessions',
91 ADMIN_PREFIX + '/settings/sessions',
93 'admin_settings_sessions_cleanup':
92 'admin_settings_sessions_cleanup':
94 ADMIN_PREFIX + '/settings/sessions/cleanup',
93 ADMIN_PREFIX + '/settings/sessions/cleanup',
95 'admin_settings_system':
94 'admin_settings_system':
96 ADMIN_PREFIX + '/settings/system',
95 ADMIN_PREFIX + '/settings/system',
97 'admin_settings_system_update':
96 'admin_settings_system_update':
98 ADMIN_PREFIX + '/settings/system/updates',
97 ADMIN_PREFIX + '/settings/system/updates',
99 'admin_settings_open_source':
98 'admin_settings_open_source':
100 ADMIN_PREFIX + '/settings/open_source',
99 ADMIN_PREFIX + '/settings/open_source',
101
100
102
101
103 }[name].format(**kwargs)
102 }[name].format(**kwargs)
104
103
105 if params:
104 if params:
106 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
105 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
107 return base_url
106 return base_url
108
107
109
108
110 @pytest.mark.usefixtures('autologin_user', 'app')
109 @pytest.mark.usefixtures('autologin_user', 'app')
111 class TestAdminSettingsController(object):
110 class TestAdminSettingsController(object):
112
111
113 @pytest.mark.parametrize('urlname', [
112 @pytest.mark.parametrize('urlname', [
114 'admin_settings_vcs',
113 'admin_settings_vcs',
115 'admin_settings_mapping',
114 'admin_settings_mapping',
116 'admin_settings_global',
115 'admin_settings_global',
117 'admin_settings_visual',
116 'admin_settings_visual',
118 'admin_settings_email',
117 'admin_settings_email',
119 'admin_settings_hooks',
118 'admin_settings_hooks',
120 'admin_settings_search',
119 'admin_settings_search',
121 ])
120 ])
122 def test_simple_get(self, urlname):
121 def test_simple_get(self, urlname):
123 self.app.get(route_path(urlname))
122 self.app.get(route_path(urlname))
124
123
125 def test_create_custom_hook(self, csrf_token):
124 def test_create_custom_hook(self, csrf_token):
126 response = self.app.post(
125 response = self.app.post(
127 route_path('admin_settings_hooks_update'),
126 route_path('admin_settings_hooks_update'),
128 params={
127 params={
129 'new_hook_ui_key': 'test_hooks_1',
128 'new_hook_ui_key': 'test_hooks_1',
130 'new_hook_ui_value': 'cd /tmp',
129 'new_hook_ui_value': 'cd /tmp',
131 'csrf_token': csrf_token})
130 'csrf_token': csrf_token})
132
131
133 response = response.follow()
132 response = response.follow()
134 response.mustcontain('test_hooks_1')
133 response.mustcontain('test_hooks_1')
135 response.mustcontain('cd /tmp')
134 response.mustcontain('cd /tmp')
136
135
137 def test_create_custom_hook_delete(self, csrf_token):
136 def test_create_custom_hook_delete(self, csrf_token):
138 response = self.app.post(
137 response = self.app.post(
139 route_path('admin_settings_hooks_update'),
138 route_path('admin_settings_hooks_update'),
140 params={
139 params={
141 'new_hook_ui_key': 'test_hooks_2',
140 'new_hook_ui_key': 'test_hooks_2',
142 'new_hook_ui_value': 'cd /tmp2',
141 'new_hook_ui_value': 'cd /tmp2',
143 'csrf_token': csrf_token})
142 'csrf_token': csrf_token})
144
143
145 response = response.follow()
144 response = response.follow()
146 response.mustcontain('test_hooks_2')
145 response.mustcontain('test_hooks_2')
147 response.mustcontain('cd /tmp2')
146 response.mustcontain('cd /tmp2')
148
147
149 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
148 hook_id = SettingsModel().get_ui_by_key('test_hooks_2').ui_id
150
149
151 # delete
150 # delete
152 self.app.post(
151 self.app.post(
153 route_path('admin_settings_hooks_delete'),
152 route_path('admin_settings_hooks_delete'),
154 params={'hook_id': hook_id, 'csrf_token': csrf_token})
153 params={'hook_id': hook_id, 'csrf_token': csrf_token})
155 response = self.app.get(route_path('admin_settings_hooks'))
154 response = self.app.get(route_path('admin_settings_hooks'))
156 response.mustcontain(no=['test_hooks_2'])
155 response.mustcontain(no=['test_hooks_2'])
157 response.mustcontain(no=['cd /tmp2'])
156 response.mustcontain(no=['cd /tmp2'])
158
157
159
158
160 @pytest.mark.usefixtures('autologin_user', 'app')
159 @pytest.mark.usefixtures('autologin_user', 'app')
161 class TestAdminSettingsGlobal(object):
160 class TestAdminSettingsGlobal(object):
162
161
163 def test_pre_post_code_code_active(self, csrf_token):
162 def test_pre_post_code_code_active(self, csrf_token):
164 pre_code = 'rc-pre-code-187652122'
163 pre_code = 'rc-pre-code-187652122'
165 post_code = 'rc-postcode-98165231'
164 post_code = 'rc-postcode-98165231'
166
165
167 response = self.post_and_verify_settings({
166 response = self.post_and_verify_settings({
168 'rhodecode_pre_code': pre_code,
167 'rhodecode_pre_code': pre_code,
169 'rhodecode_post_code': post_code,
168 'rhodecode_post_code': post_code,
170 'csrf_token': csrf_token,
169 'csrf_token': csrf_token,
171 })
170 })
172
171
173 response = response.follow()
172 response = response.follow()
174 response.mustcontain(pre_code, post_code)
173 response.mustcontain(pre_code, post_code)
175
174
176 def test_pre_post_code_code_inactive(self, csrf_token):
175 def test_pre_post_code_code_inactive(self, csrf_token):
177 pre_code = 'rc-pre-code-187652122'
176 pre_code = 'rc-pre-code-187652122'
178 post_code = 'rc-postcode-98165231'
177 post_code = 'rc-postcode-98165231'
179 response = self.post_and_verify_settings({
178 response = self.post_and_verify_settings({
180 'rhodecode_pre_code': '',
179 'rhodecode_pre_code': '',
181 'rhodecode_post_code': '',
180 'rhodecode_post_code': '',
182 'csrf_token': csrf_token,
181 'csrf_token': csrf_token,
183 })
182 })
184
183
185 response = response.follow()
184 response = response.follow()
186 response.mustcontain(no=[pre_code, post_code])
185 response.mustcontain(no=[pre_code, post_code])
187
186
188 def test_captcha_activate(self, csrf_token):
187 def test_captcha_activate(self, csrf_token):
189 self.post_and_verify_settings({
188 self.post_and_verify_settings({
190 'rhodecode_captcha_private_key': '1234567890',
189 'rhodecode_captcha_private_key': '1234567890',
191 'rhodecode_captcha_public_key': '1234567890',
190 'rhodecode_captcha_public_key': '1234567890',
192 'csrf_token': csrf_token,
191 'csrf_token': csrf_token,
193 })
192 })
194
193
195 response = self.app.get(ADMIN_PREFIX + '/register')
194 response = self.app.get(ADMIN_PREFIX + '/register')
196 response.mustcontain('captcha')
195 response.mustcontain('captcha')
197
196
198 def test_captcha_deactivate(self, csrf_token):
197 def test_captcha_deactivate(self, csrf_token):
199 self.post_and_verify_settings({
198 self.post_and_verify_settings({
200 'rhodecode_captcha_private_key': '',
199 'rhodecode_captcha_private_key': '',
201 'rhodecode_captcha_public_key': '1234567890',
200 'rhodecode_captcha_public_key': '1234567890',
202 'csrf_token': csrf_token,
201 'csrf_token': csrf_token,
203 })
202 })
204
203
205 response = self.app.get(ADMIN_PREFIX + '/register')
204 response = self.app.get(ADMIN_PREFIX + '/register')
206 response.mustcontain(no=['captcha'])
205 response.mustcontain(no=['captcha'])
207
206
208 def test_title_change(self, csrf_token):
207 def test_title_change(self, csrf_token):
209 old_title = 'RhodeCode'
208 old_title = 'RhodeCode'
210
209
211 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
210 for new_title in ['Changed', 'Ε»Γ³Ε‚wik', old_title]:
212 response = self.post_and_verify_settings({
211 response = self.post_and_verify_settings({
213 'rhodecode_title': new_title,
212 'rhodecode_title': new_title,
214 'csrf_token': csrf_token,
213 'csrf_token': csrf_token,
215 })
214 })
216
215
217 response = response.follow()
216 response = response.follow()
218 response.mustcontain(
217 response.mustcontain(
219 """<div class="branding">- %s</div>""" % new_title)
218 """<div class="branding">- %s</div>""" % new_title)
220
219
221 def post_and_verify_settings(self, settings):
220 def post_and_verify_settings(self, settings):
222 old_title = 'RhodeCode'
221 old_title = 'RhodeCode'
223 old_realm = 'RhodeCode authentication'
222 old_realm = 'RhodeCode authentication'
224 params = {
223 params = {
225 'rhodecode_title': old_title,
224 'rhodecode_title': old_title,
226 'rhodecode_realm': old_realm,
225 'rhodecode_realm': old_realm,
227 'rhodecode_pre_code': '',
226 'rhodecode_pre_code': '',
228 'rhodecode_post_code': '',
227 'rhodecode_post_code': '',
229 'rhodecode_captcha_private_key': '',
228 'rhodecode_captcha_private_key': '',
230 'rhodecode_captcha_public_key': '',
229 'rhodecode_captcha_public_key': '',
231 'rhodecode_create_personal_repo_group': False,
230 'rhodecode_create_personal_repo_group': False,
232 'rhodecode_personal_repo_group_pattern': '${username}',
231 'rhodecode_personal_repo_group_pattern': '${username}',
233 }
232 }
234 params.update(settings)
233 params.update(settings)
235 response = self.app.post(
234 response = self.app.post(
236 route_path('admin_settings_global_update'), params=params)
235 route_path('admin_settings_global_update'), params=params)
237
236
238 assert_session_flash(response, 'Updated application settings')
237 assert_session_flash(response, 'Updated application settings')
239 app_settings = SettingsModel().get_all_settings()
238 app_settings = SettingsModel().get_all_settings()
240 del settings['csrf_token']
239 del settings['csrf_token']
241 for key, value in settings.iteritems():
240 for key, value in settings.iteritems():
242 assert app_settings[key] == value.decode('utf-8')
241 assert app_settings[key] == value.decode('utf-8')
243
242
244 return response
243 return response
245
244
246
245
247 @pytest.mark.usefixtures('autologin_user', 'app')
246 @pytest.mark.usefixtures('autologin_user', 'app')
248 class TestAdminSettingsVcs(object):
247 class TestAdminSettingsVcs(object):
249
248
250 def test_contains_svn_default_patterns(self):
249 def test_contains_svn_default_patterns(self):
251 response = self.app.get(route_path('admin_settings_vcs'))
250 response = self.app.get(route_path('admin_settings_vcs'))
252 expected_patterns = [
251 expected_patterns = [
253 '/trunk',
252 '/trunk',
254 '/branches/*',
253 '/branches/*',
255 '/tags/*',
254 '/tags/*',
256 ]
255 ]
257 for pattern in expected_patterns:
256 for pattern in expected_patterns:
258 response.mustcontain(pattern)
257 response.mustcontain(pattern)
259
258
260 def test_add_new_svn_branch_and_tag_pattern(
259 def test_add_new_svn_branch_and_tag_pattern(
261 self, backend_svn, form_defaults, disable_sql_cache,
260 self, backend_svn, form_defaults, disable_sql_cache,
262 csrf_token):
261 csrf_token):
263 form_defaults.update({
262 form_defaults.update({
264 'new_svn_branch': '/exp/branches/*',
263 'new_svn_branch': '/exp/branches/*',
265 'new_svn_tag': '/important_tags/*',
264 'new_svn_tag': '/important_tags/*',
266 'csrf_token': csrf_token,
265 'csrf_token': csrf_token,
267 })
266 })
268
267
269 response = self.app.post(
268 response = self.app.post(
270 route_path('admin_settings_vcs_update'),
269 route_path('admin_settings_vcs_update'),
271 params=form_defaults, status=302)
270 params=form_defaults, status=302)
272 response = response.follow()
271 response = response.follow()
273
272
274 # Expect to find the new values on the page
273 # Expect to find the new values on the page
275 response.mustcontain('/exp/branches/*')
274 response.mustcontain('/exp/branches/*')
276 response.mustcontain('/important_tags/*')
275 response.mustcontain('/important_tags/*')
277
276
278 # Expect that those patterns are used to match branches and tags now
277 # Expect that those patterns are used to match branches and tags now
279 repo = backend_svn['svn-simple-layout'].scm_instance()
278 repo = backend_svn['svn-simple-layout'].scm_instance()
280 assert 'exp/branches/exp-sphinx-docs' in repo.branches
279 assert 'exp/branches/exp-sphinx-docs' in repo.branches
281 assert 'important_tags/v0.5' in repo.tags
280 assert 'important_tags/v0.5' in repo.tags
282
281
283 def test_add_same_svn_value_twice_shows_an_error_message(
282 def test_add_same_svn_value_twice_shows_an_error_message(
284 self, form_defaults, csrf_token, settings_util):
283 self, form_defaults, csrf_token, settings_util):
285 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
284 settings_util.create_rhodecode_ui('vcs_svn_branch', '/test')
286 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
285 settings_util.create_rhodecode_ui('vcs_svn_tag', '/test')
287
286
288 response = self.app.post(
287 response = self.app.post(
289 route_path('admin_settings_vcs_update'),
288 route_path('admin_settings_vcs_update'),
290 params={
289 params={
291 'paths_root_path': form_defaults['paths_root_path'],
290 'paths_root_path': form_defaults['paths_root_path'],
292 'new_svn_branch': '/test',
291 'new_svn_branch': '/test',
293 'new_svn_tag': '/test',
292 'new_svn_tag': '/test',
294 'csrf_token': csrf_token,
293 'csrf_token': csrf_token,
295 },
294 },
296 status=200)
295 status=200)
297
296
298 response.mustcontain("Pattern already exists")
297 response.mustcontain("Pattern already exists")
299 response.mustcontain("Some form inputs contain invalid data.")
298 response.mustcontain("Some form inputs contain invalid data.")
300
299
301 @pytest.mark.parametrize('section', [
300 @pytest.mark.parametrize('section', [
302 'vcs_svn_branch',
301 'vcs_svn_branch',
303 'vcs_svn_tag',
302 'vcs_svn_tag',
304 ])
303 ])
305 def test_delete_svn_patterns(
304 def test_delete_svn_patterns(
306 self, section, csrf_token, settings_util):
305 self, section, csrf_token, settings_util):
307 setting = settings_util.create_rhodecode_ui(
306 setting = settings_util.create_rhodecode_ui(
308 section, '/test_delete', cleanup=False)
307 section, '/test_delete', cleanup=False)
309
308
310 self.app.post(
309 self.app.post(
311 route_path('admin_settings_vcs_svn_pattern_delete'),
310 route_path('admin_settings_vcs_svn_pattern_delete'),
312 params={
311 params={
313 'delete_svn_pattern': setting.ui_id,
312 'delete_svn_pattern': setting.ui_id,
314 'csrf_token': csrf_token},
313 'csrf_token': csrf_token},
315 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
314 headers={'X-REQUESTED-WITH': 'XMLHttpRequest'})
316
315
317 @pytest.mark.parametrize('section', [
316 @pytest.mark.parametrize('section', [
318 'vcs_svn_branch',
317 'vcs_svn_branch',
319 'vcs_svn_tag',
318 'vcs_svn_tag',
320 ])
319 ])
321 def test_delete_svn_patterns_raises_404_when_no_xhr(
320 def test_delete_svn_patterns_raises_404_when_no_xhr(
322 self, section, csrf_token, settings_util):
321 self, section, csrf_token, settings_util):
323 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
322 setting = settings_util.create_rhodecode_ui(section, '/test_delete')
324
323
325 self.app.post(
324 self.app.post(
326 route_path('admin_settings_vcs_svn_pattern_delete'),
325 route_path('admin_settings_vcs_svn_pattern_delete'),
327 params={
326 params={
328 'delete_svn_pattern': setting.ui_id,
327 'delete_svn_pattern': setting.ui_id,
329 'csrf_token': csrf_token},
328 'csrf_token': csrf_token},
330 status=404)
329 status=404)
331
330
332 def test_extensions_hgsubversion(self, form_defaults, csrf_token):
331 def test_extensions_hgsubversion(self, form_defaults, csrf_token):
333 form_defaults.update({
332 form_defaults.update({
334 'csrf_token': csrf_token,
333 'csrf_token': csrf_token,
335 'extensions_hgsubversion': 'True',
334 'extensions_hgsubversion': 'True',
336 })
335 })
337 response = self.app.post(
336 response = self.app.post(
338 route_path('admin_settings_vcs_update'),
337 route_path('admin_settings_vcs_update'),
339 params=form_defaults,
338 params=form_defaults,
340 status=302)
339 status=302)
341
340
342 response = response.follow()
341 response = response.follow()
343 extensions_input = (
342 extensions_input = (
344 '<input id="extensions_hgsubversion" '
343 '<input id="extensions_hgsubversion" '
345 'name="extensions_hgsubversion" type="checkbox" '
344 'name="extensions_hgsubversion" type="checkbox" '
346 'value="True" checked="checked" />')
345 'value="True" checked="checked" />')
347 response.mustcontain(extensions_input)
346 response.mustcontain(extensions_input)
348
347
349 def test_extensions_hgevolve(self, form_defaults, csrf_token):
348 def test_extensions_hgevolve(self, form_defaults, csrf_token):
350 form_defaults.update({
349 form_defaults.update({
351 'csrf_token': csrf_token,
350 'csrf_token': csrf_token,
352 'extensions_evolve': 'True',
351 'extensions_evolve': 'True',
353 })
352 })
354 response = self.app.post(
353 response = self.app.post(
355 route_path('admin_settings_vcs_update'),
354 route_path('admin_settings_vcs_update'),
356 params=form_defaults,
355 params=form_defaults,
357 status=302)
356 status=302)
358
357
359 response = response.follow()
358 response = response.follow()
360 extensions_input = (
359 extensions_input = (
361 '<input id="extensions_evolve" '
360 '<input id="extensions_evolve" '
362 'name="extensions_evolve" type="checkbox" '
361 'name="extensions_evolve" type="checkbox" '
363 'value="True" checked="checked" />')
362 'value="True" checked="checked" />')
364 response.mustcontain(extensions_input)
363 response.mustcontain(extensions_input)
365
364
366 def test_has_a_section_for_pull_request_settings(self):
365 def test_has_a_section_for_pull_request_settings(self):
367 response = self.app.get(route_path('admin_settings_vcs'))
366 response = self.app.get(route_path('admin_settings_vcs'))
368 response.mustcontain('Pull Request Settings')
367 response.mustcontain('Pull Request Settings')
369
368
370 def test_has_an_input_for_invalidation_of_inline_comments(self):
369 def test_has_an_input_for_invalidation_of_inline_comments(self):
371 response = self.app.get(route_path('admin_settings_vcs'))
370 response = self.app.get(route_path('admin_settings_vcs'))
372 assert_response = AssertResponse(response)
371 assert_response = AssertResponse(response)
373 assert_response.one_element_exists(
372 assert_response.one_element_exists(
374 '[name=rhodecode_use_outdated_comments]')
373 '[name=rhodecode_use_outdated_comments]')
375
374
376 @pytest.mark.parametrize('new_value', [True, False])
375 @pytest.mark.parametrize('new_value', [True, False])
377 def test_allows_to_change_invalidation_of_inline_comments(
376 def test_allows_to_change_invalidation_of_inline_comments(
378 self, form_defaults, csrf_token, new_value):
377 self, form_defaults, csrf_token, new_value):
379 setting_key = 'use_outdated_comments'
378 setting_key = 'use_outdated_comments'
380 setting = SettingsModel().create_or_update_setting(
379 setting = SettingsModel().create_or_update_setting(
381 setting_key, not new_value, 'bool')
380 setting_key, not new_value, 'bool')
382 Session().add(setting)
381 Session().add(setting)
383 Session().commit()
382 Session().commit()
384
383
385 form_defaults.update({
384 form_defaults.update({
386 'csrf_token': csrf_token,
385 'csrf_token': csrf_token,
387 'rhodecode_use_outdated_comments': str(new_value),
386 'rhodecode_use_outdated_comments': str(new_value),
388 })
387 })
389 response = self.app.post(
388 response = self.app.post(
390 route_path('admin_settings_vcs_update'),
389 route_path('admin_settings_vcs_update'),
391 params=form_defaults,
390 params=form_defaults,
392 status=302)
391 status=302)
393 response = response.follow()
392 response = response.follow()
394 setting = SettingsModel().get_setting_by_name(setting_key)
393 setting = SettingsModel().get_setting_by_name(setting_key)
395 assert setting.app_settings_value is new_value
394 assert setting.app_settings_value is new_value
396
395
397 @pytest.mark.parametrize('new_value', [True, False])
396 @pytest.mark.parametrize('new_value', [True, False])
398 def test_allows_to_change_hg_rebase_merge_strategy(
397 def test_allows_to_change_hg_rebase_merge_strategy(
399 self, form_defaults, csrf_token, new_value):
398 self, form_defaults, csrf_token, new_value):
400 setting_key = 'hg_use_rebase_for_merging'
399 setting_key = 'hg_use_rebase_for_merging'
401
400
402 form_defaults.update({
401 form_defaults.update({
403 'csrf_token': csrf_token,
402 'csrf_token': csrf_token,
404 'rhodecode_' + setting_key: str(new_value),
403 'rhodecode_' + setting_key: str(new_value),
405 })
404 })
406
405
407 with mock.patch.dict(
406 with mock.patch.dict(
408 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
407 rhodecode.CONFIG, {'labs_settings_active': 'true'}):
409 self.app.post(
408 self.app.post(
410 route_path('admin_settings_vcs_update'),
409 route_path('admin_settings_vcs_update'),
411 params=form_defaults,
410 params=form_defaults,
412 status=302)
411 status=302)
413
412
414 setting = SettingsModel().get_setting_by_name(setting_key)
413 setting = SettingsModel().get_setting_by_name(setting_key)
415 assert setting.app_settings_value is new_value
414 assert setting.app_settings_value is new_value
416
415
417 @pytest.fixture
416 @pytest.fixture
418 def disable_sql_cache(self, request):
417 def disable_sql_cache(self, request):
419 patcher = mock.patch(
418 patcher = mock.patch(
420 'rhodecode.lib.caching_query.FromCache.process_query')
419 'rhodecode.lib.caching_query.FromCache.process_query')
421 request.addfinalizer(patcher.stop)
420 request.addfinalizer(patcher.stop)
422 patcher.start()
421 patcher.start()
423
422
424 @pytest.fixture
423 @pytest.fixture
425 def form_defaults(self):
424 def form_defaults(self):
426 from rhodecode.apps.admin.views.settings import AdminSettingsView
425 from rhodecode.apps.admin.views.settings import AdminSettingsView
427 return AdminSettingsView._form_defaults()
426 return AdminSettingsView._form_defaults()
428
427
429 # TODO: johbo: What we really want is to checkpoint before a test run and
428 # TODO: johbo: What we really want is to checkpoint before a test run and
430 # reset the session afterwards.
429 # reset the session afterwards.
431 @pytest.fixture(scope='class', autouse=True)
430 @pytest.fixture(scope='class', autouse=True)
432 def cleanup_settings(self, request, baseapp):
431 def cleanup_settings(self, request, baseapp):
433 ui_id = RhodeCodeUi.ui_id
432 ui_id = RhodeCodeUi.ui_id
434 original_ids = list(
433 original_ids = list(
435 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
434 r.ui_id for r in RhodeCodeUi.query().values(ui_id))
436
435
437 @request.addfinalizer
436 @request.addfinalizer
438 def cleanup():
437 def cleanup():
439 RhodeCodeUi.query().filter(
438 RhodeCodeUi.query().filter(
440 ui_id.notin_(original_ids)).delete(False)
439 ui_id.notin_(original_ids)).delete(False)
441
440
442
441
443 @pytest.mark.usefixtures('autologin_user', 'app')
442 @pytest.mark.usefixtures('autologin_user', 'app')
444 class TestLabsSettings(object):
443 class TestLabsSettings(object):
445 def test_get_settings_page_disabled(self):
444 def test_get_settings_page_disabled(self):
446 with mock.patch.dict(
445 with mock.patch.dict(
447 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
446 rhodecode.CONFIG, {'labs_settings_active': 'false'}):
448
447
449 response = self.app.get(
448 response = self.app.get(
450 route_path('admin_settings_labs'), status=302)
449 route_path('admin_settings_labs'), status=302)
451
450
452 assert response.location.endswith(route_path('admin_settings'))
451 assert response.location.endswith(route_path('admin_settings'))
453
452
454 def test_get_settings_page_enabled(self):
453 def test_get_settings_page_enabled(self):
455 from rhodecode.apps.admin.views import settings
454 from rhodecode.apps.admin.views import settings
456 lab_settings = [
455 lab_settings = [
457 settings.LabSetting(
456 settings.LabSetting(
458 key='rhodecode_bool',
457 key='rhodecode_bool',
459 type='bool',
458 type='bool',
460 group='bool group',
459 group='bool group',
461 label='bool label',
460 label='bool label',
462 help='bool help'
461 help='bool help'
463 ),
462 ),
464 settings.LabSetting(
463 settings.LabSetting(
465 key='rhodecode_text',
464 key='rhodecode_text',
466 type='unicode',
465 type='unicode',
467 group='text group',
466 group='text group',
468 label='text label',
467 label='text label',
469 help='text help'
468 help='text help'
470 ),
469 ),
471 ]
470 ]
472 with mock.patch.dict(rhodecode.CONFIG,
471 with mock.patch.dict(rhodecode.CONFIG,
473 {'labs_settings_active': 'true'}):
472 {'labs_settings_active': 'true'}):
474 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
473 with mock.patch.object(settings, '_LAB_SETTINGS', lab_settings):
475 response = self.app.get(route_path('admin_settings_labs'))
474 response = self.app.get(route_path('admin_settings_labs'))
476
475
477 assert '<label>bool group:</label>' in response
476 assert '<label>bool group:</label>' in response
478 assert '<label for="rhodecode_bool">bool label</label>' in response
477 assert '<label for="rhodecode_bool">bool label</label>' in response
479 assert '<p class="help-block">bool help</p>' in response
478 assert '<p class="help-block">bool help</p>' in response
480 assert 'name="rhodecode_bool" type="checkbox"' in response
479 assert 'name="rhodecode_bool" type="checkbox"' in response
481
480
482 assert '<label>text group:</label>' in response
481 assert '<label>text group:</label>' in response
483 assert '<label for="rhodecode_text">text label</label>' in response
482 assert '<label for="rhodecode_text">text label</label>' in response
484 assert '<p class="help-block">text help</p>' in response
483 assert '<p class="help-block">text help</p>' in response
485 assert 'name="rhodecode_text" size="60" type="text"' in response
484 assert 'name="rhodecode_text" size="60" type="text"' in response
486
485
487
486
488 @pytest.mark.usefixtures('app')
487 @pytest.mark.usefixtures('app')
489 class TestOpenSourceLicenses(object):
488 class TestOpenSourceLicenses(object):
490
489
491 def test_records_are_displayed(self, autologin_user):
490 def test_records_are_displayed(self, autologin_user):
492 sample_licenses = {
491 sample_licenses = {
493 "python2.7-pytest-2.7.1": {
492 "python2.7-pytest-2.7.1": {
494 "UNKNOWN": None
493 "UNKNOWN": None
495 },
494 },
496 "python2.7-Markdown-2.6.2": {
495 "python2.7-Markdown-2.6.2": {
497 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
496 "BSD-3-Clause": "http://spdx.org/licenses/BSD-3-Clause"
498 }
497 }
499 }
498 }
500 read_licenses_patch = mock.patch(
499 read_licenses_patch = mock.patch(
501 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
500 'rhodecode.apps.admin.views.open_source_licenses.read_opensource_licenses',
502 return_value=sample_licenses)
501 return_value=sample_licenses)
503 with read_licenses_patch:
502 with read_licenses_patch:
504 response = self.app.get(
503 response = self.app.get(
505 route_path('admin_settings_open_source'), status=200)
504 route_path('admin_settings_open_source'), status=200)
506
505
507 assert_response = AssertResponse(response)
506 assert_response = AssertResponse(response)
508 assert_response.element_contains(
507 assert_response.element_contains(
509 '.panel-heading', 'Licenses of Third Party Packages')
508 '.panel-heading', 'Licenses of Third Party Packages')
510 for name in sample_licenses:
509 for name in sample_licenses:
511 response.mustcontain(name)
510 response.mustcontain(name)
512 for license in sample_licenses[name]:
511 for license in sample_licenses[name]:
513 assert_response.element_contains('.panel-body', license)
512 assert_response.element_contains('.panel-body', license)
514
513
515 def test_records_can_be_read(self, autologin_user):
514 def test_records_can_be_read(self, autologin_user):
516 response = self.app.get(
515 response = self.app.get(
517 route_path('admin_settings_open_source'), status=200)
516 route_path('admin_settings_open_source'), status=200)
518 assert_response = AssertResponse(response)
517 assert_response = AssertResponse(response)
519 assert_response.element_contains(
518 assert_response.element_contains(
520 '.panel-heading', 'Licenses of Third Party Packages')
519 '.panel-heading', 'Licenses of Third Party Packages')
521
520
522 def test_forbidden_when_normal_user(self, autologin_regular_user):
521 def test_forbidden_when_normal_user(self, autologin_regular_user):
523 self.app.get(
522 self.app.get(
524 route_path('admin_settings_open_source'), status=404)
523 route_path('admin_settings_open_source'), status=404)
525
524
526
525
527 @pytest.mark.usefixtures('app')
526 @pytest.mark.usefixtures('app')
528 class TestUserSessions(object):
527 class TestUserSessions(object):
529
528
530 def test_forbidden_when_normal_user(self, autologin_regular_user):
529 def test_forbidden_when_normal_user(self, autologin_regular_user):
531 self.app.get(route_path('admin_settings_sessions'), status=404)
530 self.app.get(route_path('admin_settings_sessions'), status=404)
532
531
533 def test_show_sessions_page(self, autologin_user):
532 def test_show_sessions_page(self, autologin_user):
534 response = self.app.get(route_path('admin_settings_sessions'), status=200)
533 response = self.app.get(route_path('admin_settings_sessions'), status=200)
535 response.mustcontain('file')
534 response.mustcontain('file')
536
535
537 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
536 def test_cleanup_old_sessions(self, autologin_user, csrf_token):
538
537
539 post_data = {
538 post_data = {
540 'csrf_token': csrf_token,
539 'csrf_token': csrf_token,
541 'expire_days': '60'
540 'expire_days': '60'
542 }
541 }
543 response = self.app.post(
542 response = self.app.post(
544 route_path('admin_settings_sessions_cleanup'), params=post_data,
543 route_path('admin_settings_sessions_cleanup'), params=post_data,
545 status=302)
544 status=302)
546 assert_session_flash(response, 'Cleaned up old sessions')
545 assert_session_flash(response, 'Cleaned up old sessions')
547
546
548
547
549 @pytest.mark.usefixtures('app')
548 @pytest.mark.usefixtures('app')
550 class TestAdminSystemInfo(object):
549 class TestAdminSystemInfo(object):
551
550
552 def test_forbidden_when_normal_user(self, autologin_regular_user):
551 def test_forbidden_when_normal_user(self, autologin_regular_user):
553 self.app.get(route_path('admin_settings_system'), status=404)
552 self.app.get(route_path('admin_settings_system'), status=404)
554
553
555 def test_system_info_page(self, autologin_user):
554 def test_system_info_page(self, autologin_user):
556 response = self.app.get(route_path('admin_settings_system'))
555 response = self.app.get(route_path('admin_settings_system'))
557 response.mustcontain('RhodeCode Community Edition, version {}'.format(
556 response.mustcontain('RhodeCode Community Edition, version {}'.format(
558 rhodecode.__version__))
557 rhodecode.__version__))
559
558
560 def test_system_update_new_version(self, autologin_user):
559 def test_system_update_new_version(self, autologin_user):
561 update_data = {
560 update_data = {
562 'versions': [
561 'versions': [
563 {
562 {
564 'version': '100.3.1415926535',
563 'version': '100.3.1415926535',
565 'general': 'The latest version we are ever going to ship'
564 'general': 'The latest version we are ever going to ship'
566 },
565 },
567 {
566 {
568 'version': '0.0.0',
567 'version': '0.0.0',
569 'general': 'The first version we ever shipped'
568 'general': 'The first version we ever shipped'
570 }
569 }
571 ]
570 ]
572 }
571 }
573 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
572 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
574 response = self.app.get(route_path('admin_settings_system_update'))
573 response = self.app.get(route_path('admin_settings_system_update'))
575 response.mustcontain('A <b>new version</b> is available')
574 response.mustcontain('A <b>new version</b> is available')
576
575
577 def test_system_update_nothing_new(self, autologin_user):
576 def test_system_update_nothing_new(self, autologin_user):
578 update_data = {
577 update_data = {
579 'versions': [
578 'versions': [
580 {
579 {
581 'version': '0.0.0',
580 'version': '0.0.0',
582 'general': 'The first version we ever shipped'
581 'general': 'The first version we ever shipped'
583 }
582 }
584 ]
583 ]
585 }
584 }
586 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
585 with mock.patch(UPDATE_DATA_QUALNAME, return_value=update_data):
587 response = self.app.get(route_path('admin_settings_system_update'))
586 response = self.app.get(route_path('admin_settings_system_update'))
588 response.mustcontain(
587 response.mustcontain(
589 'You already have the <b>latest</b> stable version.')
588 'This instance is already running the <b>latest</b> stable version')
590
589
591 def test_system_update_bad_response(self, autologin_user):
590 def test_system_update_bad_response(self, autologin_user):
592 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
591 with mock.patch(UPDATE_DATA_QUALNAME, side_effect=ValueError('foo')):
593 response = self.app.get(route_path('admin_settings_system_update'))
592 response = self.app.get(route_path('admin_settings_system_update'))
594 response.mustcontain(
593 response.mustcontain(
595 'Bad data sent from update server')
594 'Bad data sent from update server')
596
595
597
596
598 @pytest.mark.usefixtures("app")
597 @pytest.mark.usefixtures("app")
599 class TestAdminSettingsIssueTracker(object):
598 class TestAdminSettingsIssueTracker(object):
600 RC_PREFIX = 'rhodecode_'
599 RC_PREFIX = 'rhodecode_'
601 SHORT_PATTERN_KEY = 'issuetracker_pat_'
600 SHORT_PATTERN_KEY = 'issuetracker_pat_'
602 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
601 PATTERN_KEY = RC_PREFIX + SHORT_PATTERN_KEY
603
602
604 def test_issuetracker_index(self, autologin_user):
603 def test_issuetracker_index(self, autologin_user):
605 response = self.app.get(route_path('admin_settings_issuetracker'))
604 response = self.app.get(route_path('admin_settings_issuetracker'))
606 assert response.status_code == 200
605 assert response.status_code == 200
607
606
608 def test_add_empty_issuetracker_pattern(
607 def test_add_empty_issuetracker_pattern(
609 self, request, autologin_user, csrf_token):
608 self, request, autologin_user, csrf_token):
610 post_url = route_path('admin_settings_issuetracker_update')
609 post_url = route_path('admin_settings_issuetracker_update')
611 post_data = {
610 post_data = {
612 'csrf_token': csrf_token
611 'csrf_token': csrf_token
613 }
612 }
614 self.app.post(post_url, post_data, status=302)
613 self.app.post(post_url, post_data, status=302)
615
614
616 def test_add_issuetracker_pattern(
615 def test_add_issuetracker_pattern(
617 self, request, autologin_user, csrf_token):
616 self, request, autologin_user, csrf_token):
618 pattern = 'issuetracker_pat'
617 pattern = 'issuetracker_pat'
619 another_pattern = pattern+'1'
618 another_pattern = pattern+'1'
620 post_url = route_path('admin_settings_issuetracker_update')
619 post_url = route_path('admin_settings_issuetracker_update')
621 post_data = {
620 post_data = {
622 'new_pattern_pattern_0': pattern,
621 'new_pattern_pattern_0': pattern,
623 'new_pattern_url_0': 'http://url',
622 'new_pattern_url_0': 'http://url',
624 'new_pattern_prefix_0': 'prefix',
623 'new_pattern_prefix_0': 'prefix',
625 'new_pattern_description_0': 'description',
624 'new_pattern_description_0': 'description',
626 'new_pattern_pattern_1': another_pattern,
625 'new_pattern_pattern_1': another_pattern,
627 'new_pattern_url_1': 'https://url1',
626 'new_pattern_url_1': 'https://url1',
628 'new_pattern_prefix_1': 'prefix1',
627 'new_pattern_prefix_1': 'prefix1',
629 'new_pattern_description_1': 'description1',
628 'new_pattern_description_1': 'description1',
630 'csrf_token': csrf_token
629 'csrf_token': csrf_token
631 }
630 }
632 self.app.post(post_url, post_data, status=302)
631 self.app.post(post_url, post_data, status=302)
633 settings = SettingsModel().get_all_settings()
632 settings = SettingsModel().get_all_settings()
634 self.uid = md5(pattern)
633 self.uid = md5(pattern)
635 assert settings[self.PATTERN_KEY+self.uid] == pattern
634 assert settings[self.PATTERN_KEY+self.uid] == pattern
636 self.another_uid = md5(another_pattern)
635 self.another_uid = md5(another_pattern)
637 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
636 assert settings[self.PATTERN_KEY+self.another_uid] == another_pattern
638
637
639 @request.addfinalizer
638 @request.addfinalizer
640 def cleanup():
639 def cleanup():
641 defaults = SettingsModel().get_all_settings()
640 defaults = SettingsModel().get_all_settings()
642
641
643 entries = [name for name in defaults if (
642 entries = [name for name in defaults if (
644 (self.uid in name) or (self.another_uid) in name)]
643 (self.uid in name) or (self.another_uid) in name)]
645 start = len(self.RC_PREFIX)
644 start = len(self.RC_PREFIX)
646 for del_key in entries:
645 for del_key in entries:
647 # TODO: anderson: get_by_name needs name without prefix
646 # TODO: anderson: get_by_name needs name without prefix
648 entry = SettingsModel().get_setting_by_name(del_key[start:])
647 entry = SettingsModel().get_setting_by_name(del_key[start:])
649 Session().delete(entry)
648 Session().delete(entry)
650
649
651 Session().commit()
650 Session().commit()
652
651
653 def test_edit_issuetracker_pattern(
652 def test_edit_issuetracker_pattern(
654 self, autologin_user, backend, csrf_token, request):
653 self, autologin_user, backend, csrf_token, request):
655 old_pattern = 'issuetracker_pat'
654 old_pattern = 'issuetracker_pat'
656 old_uid = md5(old_pattern)
655 old_uid = md5(old_pattern)
657 pattern = 'issuetracker_pat_new'
656 pattern = 'issuetracker_pat_new'
658 self.new_uid = md5(pattern)
657 self.new_uid = md5(pattern)
659
658
660 SettingsModel().create_or_update_setting(
659 SettingsModel().create_or_update_setting(
661 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
660 self.SHORT_PATTERN_KEY+old_uid, old_pattern, 'unicode')
662
661
663 post_url = route_path('admin_settings_issuetracker_update')
662 post_url = route_path('admin_settings_issuetracker_update')
664 post_data = {
663 post_data = {
665 'new_pattern_pattern_0': pattern,
664 'new_pattern_pattern_0': pattern,
666 'new_pattern_url_0': 'https://url',
665 'new_pattern_url_0': 'https://url',
667 'new_pattern_prefix_0': 'prefix',
666 'new_pattern_prefix_0': 'prefix',
668 'new_pattern_description_0': 'description',
667 'new_pattern_description_0': 'description',
669 'uid': old_uid,
668 'uid': old_uid,
670 'csrf_token': csrf_token
669 'csrf_token': csrf_token
671 }
670 }
672 self.app.post(post_url, post_data, status=302)
671 self.app.post(post_url, post_data, status=302)
673 settings = SettingsModel().get_all_settings()
672 settings = SettingsModel().get_all_settings()
674 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
673 assert settings[self.PATTERN_KEY+self.new_uid] == pattern
675 assert self.PATTERN_KEY+old_uid not in settings
674 assert self.PATTERN_KEY+old_uid not in settings
676
675
677 @request.addfinalizer
676 @request.addfinalizer
678 def cleanup():
677 def cleanup():
679 IssueTrackerSettingsModel().delete_entries(self.new_uid)
678 IssueTrackerSettingsModel().delete_entries(self.new_uid)
680
679
681 def test_replace_issuetracker_pattern_description(
680 def test_replace_issuetracker_pattern_description(
682 self, autologin_user, csrf_token, request, settings_util):
681 self, autologin_user, csrf_token, request, settings_util):
683 prefix = 'issuetracker'
682 prefix = 'issuetracker'
684 pattern = 'issuetracker_pat'
683 pattern = 'issuetracker_pat'
685 self.uid = md5(pattern)
684 self.uid = md5(pattern)
686 pattern_key = '_'.join([prefix, 'pat', self.uid])
685 pattern_key = '_'.join([prefix, 'pat', self.uid])
687 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
686 rc_pattern_key = '_'.join(['rhodecode', pattern_key])
688 desc_key = '_'.join([prefix, 'desc', self.uid])
687 desc_key = '_'.join([prefix, 'desc', self.uid])
689 rc_desc_key = '_'.join(['rhodecode', desc_key])
688 rc_desc_key = '_'.join(['rhodecode', desc_key])
690 new_description = 'new_description'
689 new_description = 'new_description'
691
690
692 settings_util.create_rhodecode_setting(
691 settings_util.create_rhodecode_setting(
693 pattern_key, pattern, 'unicode', cleanup=False)
692 pattern_key, pattern, 'unicode', cleanup=False)
694 settings_util.create_rhodecode_setting(
693 settings_util.create_rhodecode_setting(
695 desc_key, 'old description', 'unicode', cleanup=False)
694 desc_key, 'old description', 'unicode', cleanup=False)
696
695
697 post_url = route_path('admin_settings_issuetracker_update')
696 post_url = route_path('admin_settings_issuetracker_update')
698 post_data = {
697 post_data = {
699 'new_pattern_pattern_0': pattern,
698 'new_pattern_pattern_0': pattern,
700 'new_pattern_url_0': 'https://url',
699 'new_pattern_url_0': 'https://url',
701 'new_pattern_prefix_0': 'prefix',
700 'new_pattern_prefix_0': 'prefix',
702 'new_pattern_description_0': new_description,
701 'new_pattern_description_0': new_description,
703 'uid': self.uid,
702 'uid': self.uid,
704 'csrf_token': csrf_token
703 'csrf_token': csrf_token
705 }
704 }
706 self.app.post(post_url, post_data, status=302)
705 self.app.post(post_url, post_data, status=302)
707 settings = SettingsModel().get_all_settings()
706 settings = SettingsModel().get_all_settings()
708 assert settings[rc_pattern_key] == pattern
707 assert settings[rc_pattern_key] == pattern
709 assert settings[rc_desc_key] == new_description
708 assert settings[rc_desc_key] == new_description
710
709
711 @request.addfinalizer
710 @request.addfinalizer
712 def cleanup():
711 def cleanup():
713 IssueTrackerSettingsModel().delete_entries(self.uid)
712 IssueTrackerSettingsModel().delete_entries(self.uid)
714
713
715 def test_delete_issuetracker_pattern(
714 def test_delete_issuetracker_pattern(
716 self, autologin_user, backend, csrf_token, settings_util):
715 self, autologin_user, backend, csrf_token, settings_util):
717 pattern = 'issuetracker_pat'
716 pattern = 'issuetracker_pat'
718 uid = md5(pattern)
717 uid = md5(pattern)
719 settings_util.create_rhodecode_setting(
718 settings_util.create_rhodecode_setting(
720 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
719 self.SHORT_PATTERN_KEY+uid, pattern, 'unicode', cleanup=False)
721
720
722 post_url = route_path('admin_settings_issuetracker_delete')
721 post_url = route_path('admin_settings_issuetracker_delete')
723 post_data = {
722 post_data = {
724 '_method': 'delete',
723 '_method': 'delete',
725 'uid': uid,
724 'uid': uid,
726 'csrf_token': csrf_token
725 'csrf_token': csrf_token
727 }
726 }
728 self.app.post(post_url, post_data, status=302)
727 self.app.post(post_url, post_data, status=302)
729 settings = SettingsModel().get_all_settings()
728 settings = SettingsModel().get_all_settings()
730 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
729 assert 'rhodecode_%s%s' % (self.SHORT_PATTERN_KEY, uid) not in settings
@@ -1,208 +1,197 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 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 import logging
21 import logging
22 import urllib2
22 import urllib2
23 import packaging.version
24
23
25 from pyramid.view import view_config
24 from pyramid.view import view_config
26
25
27 import rhodecode
26 import rhodecode
28 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
29 from rhodecode.apps.admin.navigation import navigation_list
28 from rhodecode.apps.admin.navigation import navigation_list
30 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
31 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
30 from rhodecode.lib.auth import (LoginRequired, HasPermissionAllDecorator)
32 from rhodecode.lib.utils2 import str2bool
31 from rhodecode.lib.utils2 import str2bool
33 from rhodecode.lib import system_info
32 from rhodecode.lib import system_info
34 from rhodecode.lib.ext_json import json
33 from rhodecode.model.update import UpdateModel
35 from rhodecode.model.settings import SettingsModel
36
34
37 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
38
36
39
37
40 class AdminSystemInfoSettingsView(BaseAppView):
38 class AdminSystemInfoSettingsView(BaseAppView):
41 def load_default_context(self):
39 def load_default_context(self):
42 c = self._get_local_tmpl_context()
40 c = self._get_local_tmpl_context()
43
44 return c
41 return c
45
42
46 @staticmethod
47 def get_update_data(update_url):
48 """Return the JSON update data."""
49 ver = rhodecode.__version__
50 log.debug('Checking for upgrade on `%s` server', update_url)
51 opener = urllib2.build_opener()
52 opener.addheaders = [('User-agent', 'RhodeCode-SCM/%s' % ver)]
53 response = opener.open(update_url)
54 response_data = response.read()
55 data = json.loads(response_data)
56
57 return data
58
59 def get_update_url(self):
60 settings = SettingsModel().get_all_settings()
61 return settings.get('rhodecode_update_url')
62
63 @LoginRequired()
43 @LoginRequired()
64 @HasPermissionAllDecorator('hg.admin')
44 @HasPermissionAllDecorator('hg.admin')
65 @view_config(
45 @view_config(
66 route_name='admin_settings_system', request_method='GET',
46 route_name='admin_settings_system', request_method='GET',
67 renderer='rhodecode:templates/admin/settings/settings.mako')
47 renderer='rhodecode:templates/admin/settings/settings.mako')
68 def settings_system_info(self):
48 def settings_system_info(self):
69 _ = self.request.translate
49 _ = self.request.translate
70 c = self.load_default_context()
50 c = self.load_default_context()
71
51
72 c.active = 'system'
52 c.active = 'system'
73 c.navlist = navigation_list(self.request)
53 c.navlist = navigation_list(self.request)
74
54
75 # TODO(marcink), figure out how to allow only selected users to do this
55 # TODO(marcink), figure out how to allow only selected users to do this
76 c.allowed_to_snapshot = self._rhodecode_user.admin
56 c.allowed_to_snapshot = self._rhodecode_user.admin
77
57
78 snapshot = str2bool(self.request.params.get('snapshot'))
58 snapshot = str2bool(self.request.params.get('snapshot'))
79
59
80 c.rhodecode_update_url = self.get_update_url()
60 c.rhodecode_update_url = UpdateModel().get_update_url()
81 server_info = system_info.get_system_info(self.request.environ)
61 server_info = system_info.get_system_info(self.request.environ)
82
62
83 for key, val in server_info.items():
63 for key, val in server_info.items():
84 setattr(c, key, val)
64 setattr(c, key, val)
85
65
86 def val(name, subkey='human_value'):
66 def val(name, subkey='human_value'):
87 return server_info[name][subkey]
67 return server_info[name][subkey]
88
68
89 def state(name):
69 def state(name):
90 return server_info[name]['state']
70 return server_info[name]['state']
91
71
92 def val2(name):
72 def val2(name):
93 val = server_info[name]['human_value']
73 val = server_info[name]['human_value']
94 state = server_info[name]['state']
74 state = server_info[name]['state']
95 return val, state
75 return val, state
96
76
97 update_info_msg = _('Note: please make sure this server can '
77 update_info_msg = _('Note: please make sure this server can '
98 'access `${url}` for the update link to work',
78 'access `${url}` for the update link to work',
99 mapping=dict(url=c.rhodecode_update_url))
79 mapping=dict(url=c.rhodecode_update_url))
80 version = UpdateModel().get_stored_version()
81 is_outdated = UpdateModel().is_outdated(
82 rhodecode.__version__, version)
83 update_state = {
84 'type': 'warning',
85 'message': 'New version available: {}'.format(version)
86 } \
87 if is_outdated else {}
100 c.data_items = [
88 c.data_items = [
101 # update info
89 # update info
102 (_('Update info'), h.literal(
90 (_('Update info'), h.literal(
103 '<span class="link" id="check_for_update" >%s.</span>' % (
91 '<span class="link" id="check_for_update" >%s.</span>' % (
104 _('Check for updates')) +
92 _('Check for updates')) +
105 '<br/> <span >%s.</span>' % (update_info_msg)
93 '<br/> <span >%s.</span>' % (update_info_msg)
106 ), ''),
94 ), ''),
107
95
108 # RhodeCode specific
96 # RhodeCode specific
109 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
97 (_('RhodeCode Version'), val('rhodecode_app')['text'], state('rhodecode_app')),
98 (_('Latest version'), version, update_state),
110 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
99 (_('RhodeCode Server IP'), val('server')['server_ip'], state('server')),
111 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
100 (_('RhodeCode Server ID'), val('server')['server_id'], state('server')),
112 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
101 (_('RhodeCode Configuration'), val('rhodecode_config')['path'], state('rhodecode_config')),
113 (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')),
102 (_('RhodeCode Certificate'), val('rhodecode_config')['cert_path'], state('rhodecode_config')),
114 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
103 (_('Workers'), val('rhodecode_config')['config']['server:main'].get('workers', '?'), state('rhodecode_config')),
115 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
104 (_('Worker Type'), val('rhodecode_config')['config']['server:main'].get('worker_class', 'sync'), state('rhodecode_config')),
116 ('', '', ''), # spacer
105 ('', '', ''), # spacer
117
106
118 # Database
107 # Database
119 (_('Database'), val('database')['url'], state('database')),
108 (_('Database'), val('database')['url'], state('database')),
120 (_('Database version'), val('database')['version'], state('database')),
109 (_('Database version'), val('database')['version'], state('database')),
121 ('', '', ''), # spacer
110 ('', '', ''), # spacer
122
111
123 # Platform/Python
112 # Platform/Python
124 (_('Platform'), val('platform')['name'], state('platform')),
113 (_('Platform'), val('platform')['name'], state('platform')),
125 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
114 (_('Platform UUID'), val('platform')['uuid'], state('platform')),
126 (_('Python version'), val('python')['version'], state('python')),
115 (_('Python version'), val('python')['version'], state('python')),
127 (_('Python path'), val('python')['executable'], state('python')),
116 (_('Python path'), val('python')['executable'], state('python')),
128 ('', '', ''), # spacer
117 ('', '', ''), # spacer
129
118
130 # Systems stats
119 # Systems stats
131 (_('CPU'), val('cpu')['text'], state('cpu')),
120 (_('CPU'), val('cpu')['text'], state('cpu')),
132 (_('Load'), val('load')['text'], state('load')),
121 (_('Load'), val('load')['text'], state('load')),
133 (_('Memory'), val('memory')['text'], state('memory')),
122 (_('Memory'), val('memory')['text'], state('memory')),
134 (_('Uptime'), val('uptime')['text'], state('uptime')),
123 (_('Uptime'), val('uptime')['text'], state('uptime')),
135 ('', '', ''), # spacer
124 ('', '', ''), # spacer
136
125
137 # Repo storage
126 # Repo storage
138 (_('Storage location'), val('storage')['path'], state('storage')),
127 (_('Storage location'), val('storage')['path'], state('storage')),
139 (_('Storage info'), val('storage')['text'], state('storage')),
128 (_('Storage info'), val('storage')['text'], state('storage')),
140 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
129 (_('Storage inodes'), val('storage_inodes')['text'], state('storage_inodes')),
141
130
142 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
131 (_('Gist storage location'), val('storage_gist')['path'], state('storage_gist')),
143 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
132 (_('Gist storage info'), val('storage_gist')['text'], state('storage_gist')),
144
133
145 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
134 (_('Archive cache storage location'), val('storage_archive')['path'], state('storage_archive')),
146 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
135 (_('Archive cache info'), val('storage_archive')['text'], state('storage_archive')),
147
136
148 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
137 (_('Temp storage location'), val('storage_temp')['path'], state('storage_temp')),
149 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
138 (_('Temp storage info'), val('storage_temp')['text'], state('storage_temp')),
150
139
151 (_('Search info'), val('search')['text'], state('search')),
140 (_('Search info'), val('search')['text'], state('search')),
152 (_('Search location'), val('search')['location'], state('search')),
141 (_('Search location'), val('search')['location'], state('search')),
153 ('', '', ''), # spacer
142 ('', '', ''), # spacer
154
143
155 # VCS specific
144 # VCS specific
156 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
145 (_('VCS Backends'), val('vcs_backends'), state('vcs_backends')),
157 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
146 (_('VCS Server'), val('vcs_server')['text'], state('vcs_server')),
158 (_('GIT'), val('git'), state('git')),
147 (_('GIT'), val('git'), state('git')),
159 (_('HG'), val('hg'), state('hg')),
148 (_('HG'), val('hg'), state('hg')),
160 (_('SVN'), val('svn'), state('svn')),
149 (_('SVN'), val('svn'), state('svn')),
161
150
162 ]
151 ]
163
152
164 if snapshot:
153 if snapshot:
165 if c.allowed_to_snapshot:
154 if c.allowed_to_snapshot:
166 c.data_items.pop(0) # remove server info
155 c.data_items.pop(0) # remove server info
167 self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako'
156 self.request.override_renderer = 'admin/settings/settings_system_snapshot.mako'
168 else:
157 else:
169 h.flash('You are not allowed to do this', category='warning')
158 h.flash('You are not allowed to do this', category='warning')
170 return self._get_template_context(c)
159 return self._get_template_context(c)
171
160
172 @LoginRequired()
161 @LoginRequired()
173 @HasPermissionAllDecorator('hg.admin')
162 @HasPermissionAllDecorator('hg.admin')
174 @view_config(
163 @view_config(
175 route_name='admin_settings_system_update', request_method='GET',
164 route_name='admin_settings_system_update', request_method='GET',
176 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
165 renderer='rhodecode:templates/admin/settings/settings_system_update.mako')
177 def settings_system_info_check_update(self):
166 def settings_system_info_check_update(self):
178 _ = self.request.translate
167 _ = self.request.translate
179 c = self.load_default_context()
168 c = self.load_default_context()
180
169
181 update_url = self.get_update_url()
170 update_url = UpdateModel().get_update_url()
182
171
183 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">{}</div>'.format(s)
172 _err = lambda s: '<div style="color:#ff8888; padding:4px 0px">{}</div>'.format(s)
184 try:
173 try:
185 data = self.get_update_data(update_url)
174 data = UpdateModel().get_update_data(update_url)
186 except urllib2.URLError as e:
175 except urllib2.URLError as e:
187 log.exception("Exception contacting upgrade server")
176 log.exception("Exception contacting upgrade server")
188 self.request.override_renderer = 'string'
177 self.request.override_renderer = 'string'
189 return _err('Failed to contact upgrade server: %r' % e)
178 return _err('Failed to contact upgrade server: %r' % e)
190 except ValueError as e:
179 except ValueError as e:
191 log.exception("Bad data sent from update server")
180 log.exception("Bad data sent from update server")
192 self.request.override_renderer = 'string'
181 self.request.override_renderer = 'string'
193 return _err('Bad data sent from update server')
182 return _err('Bad data sent from update server')
194
183
195 latest = data['versions'][0]
184 latest = data['versions'][0]
196
185
197 c.update_url = update_url
186 c.update_url = update_url
198 c.latest_data = latest
187 c.latest_data = latest
199 c.latest_ver = latest['version']
188 c.latest_ver = latest['version']
200 c.cur_ver = rhodecode.__version__
189 c.cur_ver = rhodecode.__version__
201 c.should_upgrade = False
190 c.should_upgrade = False
202
191
203 if (packaging.version.Version(c.latest_ver) >
192 is_oudated = UpdateModel().is_outdated(c.cur_ver, c.latest_ver)
204 packaging.version.Version(c.cur_ver)):
193 if is_oudated:
205 c.should_upgrade = True
194 c.should_upgrade = True
206 c.important_notices = latest['general']
195 c.important_notices = latest['general']
207
196 UpdateModel().store_version(latest['version'])
208 return self._get_template_context(c)
197 return self._get_template_context(c)
@@ -1,285 +1,299 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2017 RhodeCode GmbH
3 # Copyright (C) 2012-2017 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 import os
26 import os
27 import time
27 import time
28
28
29 import rhodecode
29 import rhodecode
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask
31 from rhodecode.lib.celerylib import get_logger, async_task, RequestContextTask
32 from rhodecode.lib.hooks_base import log_create_repository
32 from rhodecode.lib.hooks_base import log_create_repository
33 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
33 from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
34 from rhodecode.lib.utils2 import safe_int, str2bool
34 from rhodecode.lib.utils2 import safe_int, str2bool
35 from rhodecode.model.db import Session, IntegrityError, Repository, User
35 from rhodecode.model.db import Session, IntegrityError, Repository, User
36
36
37
37
38 @async_task(ignore_result=True, base=RequestContextTask)
38 @async_task(ignore_result=True, base=RequestContextTask)
39 def send_email(recipients, subject, body='', html_body='', email_config=None):
39 def send_email(recipients, subject, body='', html_body='', email_config=None):
40 """
40 """
41 Sends an email with defined parameters from the .ini files.
41 Sends an email with defined parameters from the .ini files.
42
42
43 :param recipients: list of recipients, it this is empty the defined email
43 :param recipients: list of recipients, it this is empty the defined email
44 address from field 'email_to' is used instead
44 address from field 'email_to' is used instead
45 :param subject: subject of the mail
45 :param subject: subject of the mail
46 :param body: body of the mail
46 :param body: body of the mail
47 :param html_body: html version of body
47 :param html_body: html version of body
48 """
48 """
49 log = get_logger(send_email)
49 log = get_logger(send_email)
50
50
51 email_config = email_config or rhodecode.CONFIG
51 email_config = email_config or rhodecode.CONFIG
52 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
52 subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
53 if not recipients:
53 if not recipients:
54 # if recipients are not defined we send to email_config + all admins
54 # if recipients are not defined we send to email_config + all admins
55 admins = [
55 admins = [
56 u.email for u in User.query().filter(User.admin == True).all()]
56 u.email for u in User.query().filter(User.admin == True).all()]
57 recipients = [email_config.get('email_to')] + admins
57 recipients = [email_config.get('email_to')] + admins
58
58
59 mail_server = email_config.get('smtp_server') or None
59 mail_server = email_config.get('smtp_server') or None
60 if mail_server is None:
60 if mail_server is None:
61 log.error("SMTP server information missing. Sending email failed. "
61 log.error("SMTP server information missing. Sending email failed. "
62 "Make sure that `smtp_server` variable is configured "
62 "Make sure that `smtp_server` variable is configured "
63 "inside the .ini file")
63 "inside the .ini file")
64 return False
64 return False
65
65
66 mail_from = email_config.get('app_email_from', 'RhodeCode')
66 mail_from = email_config.get('app_email_from', 'RhodeCode')
67 user = email_config.get('smtp_username')
67 user = email_config.get('smtp_username')
68 passwd = email_config.get('smtp_password')
68 passwd = email_config.get('smtp_password')
69 mail_port = email_config.get('smtp_port')
69 mail_port = email_config.get('smtp_port')
70 tls = str2bool(email_config.get('smtp_use_tls'))
70 tls = str2bool(email_config.get('smtp_use_tls'))
71 ssl = str2bool(email_config.get('smtp_use_ssl'))
71 ssl = str2bool(email_config.get('smtp_use_ssl'))
72 debug = str2bool(email_config.get('debug'))
72 debug = str2bool(email_config.get('debug'))
73 smtp_auth = email_config.get('smtp_auth')
73 smtp_auth = email_config.get('smtp_auth')
74
74
75 try:
75 try:
76 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
76 m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
77 mail_port, ssl, tls, debug=debug)
77 mail_port, ssl, tls, debug=debug)
78 m.send(recipients, subject, body, html_body)
78 m.send(recipients, subject, body, html_body)
79 except Exception:
79 except Exception:
80 log.exception('Mail sending failed')
80 log.exception('Mail sending failed')
81 return False
81 return False
82 return True
82 return True
83
83
84
84
85 @async_task(ignore_result=True, base=RequestContextTask)
85 @async_task(ignore_result=True, base=RequestContextTask)
86 def create_repo(form_data, cur_user):
86 def create_repo(form_data, cur_user):
87 from rhodecode.model.repo import RepoModel
87 from rhodecode.model.repo import RepoModel
88 from rhodecode.model.user import UserModel
88 from rhodecode.model.user import UserModel
89 from rhodecode.model.settings import SettingsModel
89 from rhodecode.model.settings import SettingsModel
90
90
91 log = get_logger(create_repo)
91 log = get_logger(create_repo)
92
92
93 cur_user = UserModel()._get_user(cur_user)
93 cur_user = UserModel()._get_user(cur_user)
94 owner = cur_user
94 owner = cur_user
95
95
96 repo_name = form_data['repo_name']
96 repo_name = form_data['repo_name']
97 repo_name_full = form_data['repo_name_full']
97 repo_name_full = form_data['repo_name_full']
98 repo_type = form_data['repo_type']
98 repo_type = form_data['repo_type']
99 description = form_data['repo_description']
99 description = form_data['repo_description']
100 private = form_data['repo_private']
100 private = form_data['repo_private']
101 clone_uri = form_data.get('clone_uri')
101 clone_uri = form_data.get('clone_uri')
102 repo_group = safe_int(form_data['repo_group'])
102 repo_group = safe_int(form_data['repo_group'])
103 landing_rev = form_data['repo_landing_rev']
103 landing_rev = form_data['repo_landing_rev']
104 copy_fork_permissions = form_data.get('copy_permissions')
104 copy_fork_permissions = form_data.get('copy_permissions')
105 copy_group_permissions = form_data.get('repo_copy_permissions')
105 copy_group_permissions = form_data.get('repo_copy_permissions')
106 fork_of = form_data.get('fork_parent_id')
106 fork_of = form_data.get('fork_parent_id')
107 state = form_data.get('repo_state', Repository.STATE_PENDING)
107 state = form_data.get('repo_state', Repository.STATE_PENDING)
108
108
109 # repo creation defaults, private and repo_type are filled in form
109 # repo creation defaults, private and repo_type are filled in form
110 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
110 defs = SettingsModel().get_default_repo_settings(strip_prefix=True)
111 enable_statistics = form_data.get(
111 enable_statistics = form_data.get(
112 'enable_statistics', defs.get('repo_enable_statistics'))
112 'enable_statistics', defs.get('repo_enable_statistics'))
113 enable_locking = form_data.get(
113 enable_locking = form_data.get(
114 'enable_locking', defs.get('repo_enable_locking'))
114 'enable_locking', defs.get('repo_enable_locking'))
115 enable_downloads = form_data.get(
115 enable_downloads = form_data.get(
116 'enable_downloads', defs.get('repo_enable_downloads'))
116 'enable_downloads', defs.get('repo_enable_downloads'))
117
117
118 try:
118 try:
119 repo = RepoModel()._create_repo(
119 repo = RepoModel()._create_repo(
120 repo_name=repo_name_full,
120 repo_name=repo_name_full,
121 repo_type=repo_type,
121 repo_type=repo_type,
122 description=description,
122 description=description,
123 owner=owner,
123 owner=owner,
124 private=private,
124 private=private,
125 clone_uri=clone_uri,
125 clone_uri=clone_uri,
126 repo_group=repo_group,
126 repo_group=repo_group,
127 landing_rev=landing_rev,
127 landing_rev=landing_rev,
128 fork_of=fork_of,
128 fork_of=fork_of,
129 copy_fork_permissions=copy_fork_permissions,
129 copy_fork_permissions=copy_fork_permissions,
130 copy_group_permissions=copy_group_permissions,
130 copy_group_permissions=copy_group_permissions,
131 enable_statistics=enable_statistics,
131 enable_statistics=enable_statistics,
132 enable_locking=enable_locking,
132 enable_locking=enable_locking,
133 enable_downloads=enable_downloads,
133 enable_downloads=enable_downloads,
134 state=state
134 state=state
135 )
135 )
136 Session().commit()
136 Session().commit()
137
137
138 # now create this repo on Filesystem
138 # now create this repo on Filesystem
139 RepoModel()._create_filesystem_repo(
139 RepoModel()._create_filesystem_repo(
140 repo_name=repo_name,
140 repo_name=repo_name,
141 repo_type=repo_type,
141 repo_type=repo_type,
142 repo_group=RepoModel()._get_repo_group(repo_group),
142 repo_group=RepoModel()._get_repo_group(repo_group),
143 clone_uri=clone_uri,
143 clone_uri=clone_uri,
144 )
144 )
145 repo = Repository.get_by_repo_name(repo_name_full)
145 repo = Repository.get_by_repo_name(repo_name_full)
146 log_create_repository(created_by=owner.username, **repo.get_dict())
146 log_create_repository(created_by=owner.username, **repo.get_dict())
147
147
148 # update repo commit caches initially
148 # update repo commit caches initially
149 repo.update_commit_cache()
149 repo.update_commit_cache()
150
150
151 # set new created state
151 # set new created state
152 repo.set_state(Repository.STATE_CREATED)
152 repo.set_state(Repository.STATE_CREATED)
153 repo_id = repo.repo_id
153 repo_id = repo.repo_id
154 repo_data = repo.get_api_data()
154 repo_data = repo.get_api_data()
155
155
156 audit_logger.store(
156 audit_logger.store(
157 'repo.create', action_data={'data': repo_data},
157 'repo.create', action_data={'data': repo_data},
158 user=cur_user,
158 user=cur_user,
159 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
159 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
160
160
161 Session().commit()
161 Session().commit()
162 except Exception as e:
162 except Exception as e:
163 log.warning('Exception occurred when creating repository, '
163 log.warning('Exception occurred when creating repository, '
164 'doing cleanup...', exc_info=True)
164 'doing cleanup...', exc_info=True)
165 if isinstance(e, IntegrityError):
165 if isinstance(e, IntegrityError):
166 Session().rollback()
166 Session().rollback()
167
167
168 # rollback things manually !
168 # rollback things manually !
169 repo = Repository.get_by_repo_name(repo_name_full)
169 repo = Repository.get_by_repo_name(repo_name_full)
170 if repo:
170 if repo:
171 Repository.delete(repo.repo_id)
171 Repository.delete(repo.repo_id)
172 Session().commit()
172 Session().commit()
173 RepoModel()._delete_filesystem_repo(repo)
173 RepoModel()._delete_filesystem_repo(repo)
174 log.info('Cleanup of repo %s finished', repo_name_full)
174 log.info('Cleanup of repo %s finished', repo_name_full)
175 raise
175 raise
176
176
177 return True
177 return True
178
178
179
179
180 @async_task(ignore_result=True, base=RequestContextTask)
180 @async_task(ignore_result=True, base=RequestContextTask)
181 def create_repo_fork(form_data, cur_user):
181 def create_repo_fork(form_data, cur_user):
182 """
182 """
183 Creates a fork of repository using internal VCS methods
183 Creates a fork of repository using internal VCS methods
184 """
184 """
185 from rhodecode.model.repo import RepoModel
185 from rhodecode.model.repo import RepoModel
186 from rhodecode.model.user import UserModel
186 from rhodecode.model.user import UserModel
187
187
188 log = get_logger(create_repo_fork)
188 log = get_logger(create_repo_fork)
189
189
190 cur_user = UserModel()._get_user(cur_user)
190 cur_user = UserModel()._get_user(cur_user)
191 owner = cur_user
191 owner = cur_user
192
192
193 repo_name = form_data['repo_name'] # fork in this case
193 repo_name = form_data['repo_name'] # fork in this case
194 repo_name_full = form_data['repo_name_full']
194 repo_name_full = form_data['repo_name_full']
195 repo_type = form_data['repo_type']
195 repo_type = form_data['repo_type']
196 description = form_data['description']
196 description = form_data['description']
197 private = form_data['private']
197 private = form_data['private']
198 clone_uri = form_data.get('clone_uri')
198 clone_uri = form_data.get('clone_uri')
199 repo_group = safe_int(form_data['repo_group'])
199 repo_group = safe_int(form_data['repo_group'])
200 landing_rev = form_data['landing_rev']
200 landing_rev = form_data['landing_rev']
201 copy_fork_permissions = form_data.get('copy_permissions')
201 copy_fork_permissions = form_data.get('copy_permissions')
202 fork_id = safe_int(form_data.get('fork_parent_id'))
202 fork_id = safe_int(form_data.get('fork_parent_id'))
203
203
204 try:
204 try:
205 fork_of = RepoModel()._get_repo(fork_id)
205 fork_of = RepoModel()._get_repo(fork_id)
206 RepoModel()._create_repo(
206 RepoModel()._create_repo(
207 repo_name=repo_name_full,
207 repo_name=repo_name_full,
208 repo_type=repo_type,
208 repo_type=repo_type,
209 description=description,
209 description=description,
210 owner=owner,
210 owner=owner,
211 private=private,
211 private=private,
212 clone_uri=clone_uri,
212 clone_uri=clone_uri,
213 repo_group=repo_group,
213 repo_group=repo_group,
214 landing_rev=landing_rev,
214 landing_rev=landing_rev,
215 fork_of=fork_of,
215 fork_of=fork_of,
216 copy_fork_permissions=copy_fork_permissions
216 copy_fork_permissions=copy_fork_permissions
217 )
217 )
218
218
219 Session().commit()
219 Session().commit()
220
220
221 base_path = Repository.base_path()
221 base_path = Repository.base_path()
222 source_repo_path = os.path.join(base_path, fork_of.repo_name)
222 source_repo_path = os.path.join(base_path, fork_of.repo_name)
223
223
224 # now create this repo on Filesystem
224 # now create this repo on Filesystem
225 RepoModel()._create_filesystem_repo(
225 RepoModel()._create_filesystem_repo(
226 repo_name=repo_name,
226 repo_name=repo_name,
227 repo_type=repo_type,
227 repo_type=repo_type,
228 repo_group=RepoModel()._get_repo_group(repo_group),
228 repo_group=RepoModel()._get_repo_group(repo_group),
229 clone_uri=source_repo_path,
229 clone_uri=source_repo_path,
230 )
230 )
231 repo = Repository.get_by_repo_name(repo_name_full)
231 repo = Repository.get_by_repo_name(repo_name_full)
232 log_create_repository(created_by=owner.username, **repo.get_dict())
232 log_create_repository(created_by=owner.username, **repo.get_dict())
233
233
234 # update repo commit caches initially
234 # update repo commit caches initially
235 config = repo._config
235 config = repo._config
236 config.set('extensions', 'largefiles', '')
236 config.set('extensions', 'largefiles', '')
237 repo.update_commit_cache(config=config)
237 repo.update_commit_cache(config=config)
238
238
239 # set new created state
239 # set new created state
240 repo.set_state(Repository.STATE_CREATED)
240 repo.set_state(Repository.STATE_CREATED)
241
241
242 repo_id = repo.repo_id
242 repo_id = repo.repo_id
243 repo_data = repo.get_api_data()
243 repo_data = repo.get_api_data()
244 audit_logger.store(
244 audit_logger.store(
245 'repo.fork', action_data={'data': repo_data},
245 'repo.fork', action_data={'data': repo_data},
246 user=cur_user,
246 user=cur_user,
247 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
247 repo=audit_logger.RepoWrap(repo_name=repo_name, repo_id=repo_id))
248
248
249 Session().commit()
249 Session().commit()
250 except Exception as e:
250 except Exception as e:
251 log.warning('Exception %s occurred when forking repository, '
251 log.warning('Exception %s occurred when forking repository, '
252 'doing cleanup...', exc_info=True)
252 'doing cleanup...', exc_info=True)
253 if isinstance(e, IntegrityError):
253 if isinstance(e, IntegrityError):
254 Session().rollback()
254 Session().rollback()
255
255
256 # rollback things manually !
256 # rollback things manually !
257 repo = Repository.get_by_repo_name(repo_name_full)
257 repo = Repository.get_by_repo_name(repo_name_full)
258 if repo:
258 if repo:
259 Repository.delete(repo.repo_id)
259 Repository.delete(repo.repo_id)
260 Session().commit()
260 Session().commit()
261 RepoModel()._delete_filesystem_repo(repo)
261 RepoModel()._delete_filesystem_repo(repo)
262 log.info('Cleanup of repo %s finished', repo_name_full)
262 log.info('Cleanup of repo %s finished', repo_name_full)
263 raise
263 raise
264
264
265 return True
265 return True
266
266
267
267
268 @async_task(ignore_result=True)
268 @async_task(ignore_result=True)
269 def sync_repo(*args, **kwargs):
269 def sync_repo(*args, **kwargs):
270 from rhodecode.model.scm import ScmModel
270 from rhodecode.model.scm import ScmModel
271 log = get_logger(sync_repo)
271 log = get_logger(sync_repo)
272 repo_name = kwargs['repo_name']
272 repo_name = kwargs['repo_name']
273 log.info('Pulling from %s', repo_name)
273 log.info('Pulling from %s', repo_name)
274 dbrepo = Repository.get_by_repo_name(repo_name)
274 dbrepo = Repository.get_by_repo_name(repo_name)
275 if dbrepo and dbrepo.clone_uri:
275 if dbrepo and dbrepo.clone_uri:
276 ScmModel().pull_changes(kwargs['repo_name'], kwargs['username'])
276 ScmModel().pull_changes(kwargs['repo_name'], kwargs['username'])
277 else:
277 else:
278 log.debug('Repo `%s` not found or without a clone_url', repo_name)
278 log.debug('Repo `%s` not found or without a clone_url', repo_name)
279
279
280
280
281 @async_task(ignore_result=True)
282 def check_for_update():
283 from rhodecode.model.update import UpdateModel
284 update_url = UpdateModel().get_update_url()
285 cur_ver = rhodecode.__version__
286
287 try:
288 data = UpdateModel().get_update_data(update_url)
289 latest = data['versions'][0]
290 UpdateModel().store_version(latest['version'])
291 except Exception:
292 pass
293
294
281 @async_task(ignore_result=False)
295 @async_task(ignore_result=False)
282 def beat_check(*args, **kwargs):
296 def beat_check(*args, **kwargs):
283 log = get_logger(beat_check)
297 log = get_logger(beat_check)
284 log.info('Got args: %r and kwargs %r', args, kwargs)
298 log.info('Got args: %r and kwargs %r', args, kwargs)
285 return time.time()
299 return time.time()
@@ -1,27 +1,26 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ## upgrade block rendered afte on-click check
2 ## upgrade block rendered afte on-click check
3
3
4 <div class="alert ${'alert-warning' if c.should_upgrade else 'alert-success'}">
4 <div class="alert ${'alert-warning' if c.should_upgrade else 'alert-success'}">
5 <p >
5 <p>
6
7 %if c.should_upgrade:
6 %if c.should_upgrade:
8 A <b>new version</b> is available:
7 A <b>new version</b> is available:
9 %if c.latest_data.get('title'):
8 %if c.latest_data.get('title'):
10 <b>${h.literal(c.latest_data['title'])}</b>
9 <b>${h.literal(c.latest_data['title'])}</b>
11 %else:
10 %else:
12 <b>${c.latest_ver}</b>
11 <b>${c.latest_ver}</b>
13 %endif
12 %endif
14 %else:
13 %else:
15 You already have the <b>latest</b> stable version.
14 This instance is already running the <b>latest</b> stable version ${c.latest_ver}.
16 %endif
15 %endif
17 </p>
16 </p>
18
17
19 % if c.should_upgrade and c.important_notices:
18 % if c.should_upgrade and c.important_notices:
20 <div>Important notes for this release:</div>
19 <div>Important notes for this release:</div>
21 <ul>
20 <ul>
22 % for notice in c.important_notices:
21 % for notice in c.important_notices:
23 <li>- ${notice}</li>
22 <li>- ${notice}</li>
24 % endfor
23 % endfor
25 </ul>
24 </ul>
26 % endif
25 % endif
27 </div>
26 </div>
General Comments 0
You need to be logged in to leave comments. Login now