##// END OF EJS Templates
configs: removed utf8 markers
super-admin -
r5058:fa2cd01c default
parent child Browse files
Show More
@@ -1,20 +1,19 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
@@ -1,39 +1,39 b''
1 # -*- coding: utf-8 -*-
1
2 2
3 3 # Copyright (C) 2013-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Various config settings for RhodeCode
23 23 """
24 24 from rhodecode import EXTENSIONS
25 25
26 26 from rhodecode.lib.utils2 import __get_lem
27 27
28 28
29 29 # language map is also used by whoosh indexer, which for those specified
30 30 # extensions will index it's content
31 31 # custom extensions to lexers, format is 'ext': 'LexerClass'
32 32 extra = {
33 33 'vbs': 'VbNet'
34 34 }
35 35 LANGUAGES_EXTENSIONS_MAP = __get_lem(extra)
36 36
37 37 DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
38 38
39 39 DATE_FORMAT = "%Y-%m-%d"
@@ -1,90 +1,89 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 import os
22 21 import logging
23 22 import rhodecode
24 23 import collections
25 24
26 25 from rhodecode.config import utils
27 26
28 27 from rhodecode.lib.utils import load_rcextensions
29 28 from rhodecode.lib.utils2 import str2bool
30 29 from rhodecode.lib.vcs import connect_vcs
31 30
32 31 log = logging.getLogger(__name__)
33 32
34 33
35 34 def load_pyramid_environment(global_config, settings):
36 35 # Some parts of the code expect a merge of global and app settings.
37 36 settings_merged = global_config.copy()
38 37 settings_merged.update(settings)
39 38
40 39 # TODO(marcink): probably not required anymore
41 40 # configure channelstream,
42 41 settings_merged['channelstream_config'] = {
43 42 'enabled': str2bool(settings_merged.get('channelstream.enabled', False)),
44 43 'server': settings_merged.get('channelstream.server'),
45 44 'secret': settings_merged.get('channelstream.secret')
46 45 }
47 46
48 47 # If this is a test run we prepare the test environment like
49 48 # creating a test database, test search index and test repositories.
50 49 # This has to be done before the database connection is initialized.
51 50 if settings['is_test']:
52 51 rhodecode.is_test = True
53 52 rhodecode.disable_error_handler = True
54 53 from rhodecode import authentication
55 54 authentication.plugin_default_auth_ttl = 0
56 55
57 56 utils.initialize_test_environment(settings_merged)
58 57
59 58 # Initialize the database connection.
60 59 utils.initialize_database(settings_merged)
61 60
62 61 load_rcextensions(root_path=settings_merged['here'])
63 62
64 63 # Limit backends to `vcs.backends` from configuration, and preserve the order
65 64 for alias in rhodecode.BACKENDS.keys():
66 65 if alias not in settings['vcs.backends']:
67 66 del rhodecode.BACKENDS[alias]
68 67
69 68 _sorted_backend = sorted(rhodecode.BACKENDS.items(),
70 69 key=lambda item: settings['vcs.backends'].index(item[0]))
71 70 rhodecode.BACKENDS = collections.OrderedDict(_sorted_backend)
72 71
73 72 log.info('Enabled VCS backends: %s', rhodecode.BACKENDS.keys())
74 73
75 74 # initialize vcs client and optionally run the server if enabled
76 75 vcs_server_uri = settings['vcs.server']
77 76 vcs_server_enabled = settings['vcs.server.enable']
78 77
79 78 utils.configure_vcs(settings)
80 79
81 80 # Store the settings to make them available to other modules.
82 81
83 82 rhodecode.PYRAMID_SETTINGS = settings_merged
84 83 rhodecode.CONFIG = settings_merged
85 84 rhodecode.CONFIG['default_user_id'] = utils.get_default_user_id()
86 85
87 86 if vcs_server_enabled:
88 87 connect_vcs(vcs_server_uri, utils.get_vcs_server_protocol(settings))
89 88 else:
90 89 log.warning('vcs-server not enabled, vcs connection unavailable')
@@ -1,43 +1,42 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 def generate_jsroutes_content(jsroutes):
22 21 statements = []
23 22 for url_name, url, fields in sorted(jsroutes):
24 23 statements.append(
25 24 "pyroutes.register('%s', '%s', %s);" % (url_name, url, fields))
26 25 return u'''
27 26 /******************************************************************************
28 27 * *
29 28 * DO NOT CHANGE THIS FILE MANUALLY *
30 29 * *
31 30 * *
32 31 * This file is automatically generated when the app starts up with *
33 32 * generate_js_files = true *
34 33 * *
35 34 * To add a route here pass jsroute=True to the route definition in the app *
36 35 * *
37 36 ******************************************************************************/
38 37 function registerRCRoutes() {
39 38 // routes registration
40 39 %s
41 40 }
42 41 ''' % '\n '.join(statements)
43 42
@@ -1,617 +1,616 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 import os
22 21 import sys
23 22 import collections
24 23 import tempfile
25 24 import time
26 25 import logging.config
27 26
28 27 from paste.gzipper import make_gzip_middleware
29 28 import pyramid.events
30 29 from pyramid.wsgi import wsgiapp
31 30 from pyramid.authorization import ACLAuthorizationPolicy
32 31 from pyramid.config import Configurator
33 32 from pyramid.settings import asbool, aslist
34 33 from pyramid.httpexceptions import (
35 34 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 35 from pyramid.renderers import render_to_response
37 36
38 37 from rhodecode.model import meta
39 38 from rhodecode.config import patches
40 39 from rhodecode.config import utils as config_utils
41 40 from rhodecode.config.settings_maker import SettingsMaker
42 41 from rhodecode.config.environment import load_pyramid_environment
43 42
44 43 import rhodecode.events
45 44 from rhodecode.lib.middleware.vcs import VCSMiddleware
46 45 from rhodecode.lib.request import Request
47 46 from rhodecode.lib.vcs import VCSCommunicationError
48 47 from rhodecode.lib.exceptions import VCSServerUnavailable
49 48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
50 49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
51 50 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
52 51 from rhodecode.lib.utils2 import AttributeDict
53 52 from rhodecode.lib.exc_tracking import store_exception
54 53 from rhodecode.subscribers import (
55 54 scan_repositories_if_enabled, write_js_routes_if_enabled,
56 55 write_metadata_if_needed, write_usage_data)
57 56 from rhodecode.lib.statsd_client import StatsdClient
58 57
59 58 log = logging.getLogger(__name__)
60 59
61 60
62 61 def is_http_error(response):
63 62 # error which should have traceback
64 63 return response.status_code > 499
65 64
66 65
67 66 def should_load_all():
68 67 """
69 68 Returns if all application components should be loaded. In some cases it's
70 69 desired to skip apps loading for faster shell script execution
71 70 """
72 71 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
73 72 if ssh_cmd:
74 73 return False
75 74
76 75 return True
77 76
78 77
79 78 def make_pyramid_app(global_config, **settings):
80 79 """
81 80 Constructs the WSGI application based on Pyramid.
82 81
83 82 Specials:
84 83
85 84 * The application can also be integrated like a plugin via the call to
86 85 `includeme`. This is accompanied with the other utility functions which
87 86 are called. Changing this should be done with great care to not break
88 87 cases when these fragments are assembled from another place.
89 88
90 89 """
91 90 start_time = time.time()
92 91 log.info('Pyramid app config starting')
93 92
94 93 sanitize_settings_and_apply_defaults(global_config, settings)
95 94
96 95 # init and bootstrap StatsdClient
97 96 StatsdClient.setup(settings)
98 97
99 98 config = Configurator(settings=settings)
100 99 # Init our statsd at very start
101 100 config.registry.statsd = StatsdClient.statsd
102 101
103 102 # Apply compatibility patches
104 103 patches.inspect_getargspec()
105 104
106 105 load_pyramid_environment(global_config, settings)
107 106
108 107 # Static file view comes first
109 108 includeme_first(config)
110 109
111 110 includeme(config)
112 111
113 112 pyramid_app = config.make_wsgi_app()
114 113 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
115 114 pyramid_app.config = config
116 115
117 116 celery_settings = get_celery_config(settings)
118 117 config.configure_celery(celery_settings)
119 118
120 119 # creating the app uses a connection - return it after we are done
121 120 meta.Session.remove()
122 121
123 122 total_time = time.time() - start_time
124 123 log.info('Pyramid app `%s` created and configured in %.2fs',
125 124 getattr(pyramid_app, 'func_name', 'pyramid_app'), total_time)
126 125 return pyramid_app
127 126
128 127
129 128 def get_celery_config(settings):
130 129 """
131 130 Converts basic ini configuration into celery 4.X options
132 131 """
133 132
134 133 def key_converter(key_name):
135 134 pref = 'celery.'
136 135 if key_name.startswith(pref):
137 136 return key_name[len(pref):].replace('.', '_').lower()
138 137
139 138 def type_converter(parsed_key, value):
140 139 # cast to int
141 140 if value.isdigit():
142 141 return int(value)
143 142
144 143 # cast to bool
145 144 if value.lower() in ['true', 'false', 'True', 'False']:
146 145 return value.lower() == 'true'
147 146 return value
148 147
149 148 celery_config = {}
150 149 for k, v in settings.items():
151 150 pref = 'celery.'
152 151 if k.startswith(pref):
153 152 celery_config[key_converter(k)] = type_converter(key_converter(k), v)
154 153
155 154 # TODO:rethink if we want to support celerybeat based file config, probably NOT
156 155 # beat_config = {}
157 156 # for section in parser.sections():
158 157 # if section.startswith('celerybeat:'):
159 158 # name = section.split(':', 1)[1]
160 159 # beat_config[name] = get_beat_config(parser, section)
161 160
162 161 # final compose of settings
163 162 celery_settings = {}
164 163
165 164 if celery_config:
166 165 celery_settings.update(celery_config)
167 166 # if beat_config:
168 167 # celery_settings.update({'beat_schedule': beat_config})
169 168
170 169 return celery_settings
171 170
172 171
173 172 def not_found_view(request):
174 173 """
175 174 This creates the view which should be registered as not-found-view to
176 175 pyramid.
177 176 """
178 177
179 178 if not getattr(request, 'vcs_call', None):
180 179 # handle like regular case with our error_handler
181 180 return error_handler(HTTPNotFound(), request)
182 181
183 182 # handle not found view as a vcs call
184 183 settings = request.registry.settings
185 184 ae_client = getattr(request, 'ae_client', None)
186 185 vcs_app = VCSMiddleware(
187 186 HTTPNotFound(), request.registry, settings,
188 187 appenlight_client=ae_client)
189 188
190 189 return wsgiapp(vcs_app)(None, request)
191 190
192 191
193 192 def error_handler(exception, request):
194 193 import rhodecode
195 194 from rhodecode.lib import helpers
196 195 from rhodecode.lib.utils2 import str2bool
197 196
198 197 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
199 198
200 199 base_response = HTTPInternalServerError()
201 200 # prefer original exception for the response since it may have headers set
202 201 if isinstance(exception, HTTPException):
203 202 base_response = exception
204 203 elif isinstance(exception, VCSCommunicationError):
205 204 base_response = VCSServerUnavailable()
206 205
207 206 if is_http_error(base_response):
208 207 log.exception(
209 208 'error occurred handling this request for path: %s', request.path)
210 209
211 210 error_explanation = base_response.explanation or str(base_response)
212 211 if base_response.status_code == 404:
213 212 error_explanation += " Optionally you don't have permission to access this page."
214 213 c = AttributeDict()
215 214 c.error_message = base_response.status
216 215 c.error_explanation = error_explanation
217 216 c.visual = AttributeDict()
218 217
219 218 c.visual.rhodecode_support_url = (
220 219 request.registry.settings.get('rhodecode_support_url') or
221 220 request.route_url('rhodecode_support')
222 221 )
223 222 c.redirect_time = 0
224 223 c.rhodecode_name = rhodecode_title
225 224 if not c.rhodecode_name:
226 225 c.rhodecode_name = 'Rhodecode'
227 226
228 227 c.causes = []
229 228 if is_http_error(base_response):
230 229 c.causes.append('Server is overloaded.')
231 230 c.causes.append('Server database connection is lost.')
232 231 c.causes.append('Server expected unhandled error.')
233 232
234 233 if hasattr(base_response, 'causes'):
235 234 c.causes = base_response.causes
236 235
237 236 c.messages = helpers.flash.pop_messages(request=request)
238 237 exc_info = sys.exc_info()
239 238 c.exception_id = id(exc_info)
240 239 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
241 240 or base_response.status_code > 499
242 241 c.exception_id_url = request.route_url(
243 242 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
244 243
245 244 if c.show_exception_id:
246 245 store_exception(c.exception_id, exc_info)
247 246 c.exception_debug = str2bool(rhodecode.CONFIG.get('debug'))
248 247 c.exception_config_ini = rhodecode.CONFIG.get('__file__')
249 248
250 249 response = render_to_response(
251 250 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
252 251 response=base_response)
253 252
254 253 statsd = request.registry.statsd
255 254 if statsd and base_response.status_code > 499:
256 255 exc_type = "{}.{}".format(exception.__class__.__module__, exception.__class__.__name__)
257 256 statsd.incr('rhodecode_exception_total',
258 257 tags=["exc_source:web",
259 258 "http_code:{}".format(base_response.status_code),
260 259 "type:{}".format(exc_type)])
261 260
262 261 return response
263 262
264 263
265 264 def includeme_first(config):
266 265 # redirect automatic browser favicon.ico requests to correct place
267 266 def favicon_redirect(context, request):
268 267 return HTTPFound(
269 268 request.static_path('rhodecode:public/images/favicon.ico'))
270 269
271 270 config.add_view(favicon_redirect, route_name='favicon')
272 271 config.add_route('favicon', '/favicon.ico')
273 272
274 273 def robots_redirect(context, request):
275 274 return HTTPFound(
276 275 request.static_path('rhodecode:public/robots.txt'))
277 276
278 277 config.add_view(robots_redirect, route_name='robots')
279 278 config.add_route('robots', '/robots.txt')
280 279
281 280 config.add_static_view(
282 281 '_static/deform', 'deform:static')
283 282 config.add_static_view(
284 283 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
285 284
286 285
287 286 def includeme(config, auth_resources=None):
288 287 from rhodecode.lib.celerylib.loader import configure_celery
289 288 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
290 289 settings = config.registry.settings
291 290 config.set_request_factory(Request)
292 291
293 292 # plugin information
294 293 config.registry.rhodecode_plugins = collections.OrderedDict()
295 294
296 295 config.add_directive(
297 296 'register_rhodecode_plugin', register_rhodecode_plugin)
298 297
299 298 config.add_directive('configure_celery', configure_celery)
300 299
301 300 if settings.get('appenlight', False):
302 301 config.include('appenlight_client.ext.pyramid_tween')
303 302
304 303 load_all = should_load_all()
305 304
306 305 # Includes which are required. The application would fail without them.
307 306 config.include('pyramid_mako')
308 307 config.include('rhodecode.lib.rc_beaker')
309 308 config.include('rhodecode.lib.rc_cache')
310 309 config.include('rhodecode.apps._base.navigation')
311 310 config.include('rhodecode.apps._base.subscribers')
312 311 config.include('rhodecode.tweens')
313 312 config.include('rhodecode.authentication')
314 313
315 314 if load_all:
316 315 ce_auth_resources = [
317 316 'rhodecode.authentication.plugins.auth_crowd',
318 317 'rhodecode.authentication.plugins.auth_headers',
319 318 'rhodecode.authentication.plugins.auth_jasig_cas',
320 319 'rhodecode.authentication.plugins.auth_ldap',
321 320 'rhodecode.authentication.plugins.auth_pam',
322 321 'rhodecode.authentication.plugins.auth_rhodecode',
323 322 'rhodecode.authentication.plugins.auth_token',
324 323 ]
325 324
326 325 # load CE authentication plugins
327 326
328 327 if auth_resources:
329 328 ce_auth_resources.extend(auth_resources)
330 329
331 330 for resource in ce_auth_resources:
332 331 config.include(resource)
333 332
334 333 # Auto discover authentication plugins and include their configuration.
335 334 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
336 335 from rhodecode.authentication import discover_legacy_plugins
337 336 discover_legacy_plugins(config)
338 337
339 338 # apps
340 339 if load_all:
341 340 log.debug('Starting config.include() calls')
342 341 config.include('rhodecode.api.includeme')
343 342 config.include('rhodecode.apps._base.includeme')
344 343 config.include('rhodecode.apps._base.navigation.includeme')
345 344 config.include('rhodecode.apps._base.subscribers.includeme')
346 345 config.include('rhodecode.apps.hovercards.includeme')
347 346 config.include('rhodecode.apps.ops.includeme')
348 347 config.include('rhodecode.apps.channelstream.includeme')
349 348 config.include('rhodecode.apps.file_store.includeme')
350 349 config.include('rhodecode.apps.admin.includeme')
351 350 config.include('rhodecode.apps.login.includeme')
352 351 config.include('rhodecode.apps.home.includeme')
353 352 config.include('rhodecode.apps.journal.includeme')
354 353
355 354 config.include('rhodecode.apps.repository.includeme')
356 355 config.include('rhodecode.apps.repo_group.includeme')
357 356 config.include('rhodecode.apps.user_group.includeme')
358 357 config.include('rhodecode.apps.search.includeme')
359 358 config.include('rhodecode.apps.user_profile.includeme')
360 359 config.include('rhodecode.apps.user_group_profile.includeme')
361 360 config.include('rhodecode.apps.my_account.includeme')
362 361 config.include('rhodecode.apps.gist.includeme')
363 362
364 363 config.include('rhodecode.apps.svn_support.includeme')
365 364 config.include('rhodecode.apps.ssh_support.includeme')
366 365 config.include('rhodecode.apps.debug_style')
367 366
368 367 if load_all:
369 368 config.include('rhodecode.integrations.includeme')
370 369 config.include('rhodecode.integrations.routes.includeme')
371 370
372 371 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
373 372 settings['default_locale_name'] = settings.get('lang', 'en')
374 373 config.add_translation_dirs('rhodecode:i18n/')
375 374
376 375 # Add subscribers.
377 376 if load_all:
378 377 log.debug('Adding subscribers....')
379 378 config.add_subscriber(scan_repositories_if_enabled,
380 379 pyramid.events.ApplicationCreated)
381 380 config.add_subscriber(write_metadata_if_needed,
382 381 pyramid.events.ApplicationCreated)
383 382 config.add_subscriber(write_usage_data,
384 383 pyramid.events.ApplicationCreated)
385 384 config.add_subscriber(write_js_routes_if_enabled,
386 385 pyramid.events.ApplicationCreated)
387 386
388 387 # Set the authorization policy.
389 388 authz_policy = ACLAuthorizationPolicy()
390 389 config.set_authorization_policy(authz_policy)
391 390
392 391 # Set the default renderer for HTML templates to mako.
393 392 config.add_mako_renderer('.html')
394 393
395 394 config.add_renderer(
396 395 name='json_ext',
397 396 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
398 397
399 398 config.add_renderer(
400 399 name='string_html',
401 400 factory='rhodecode.lib.string_renderer.html')
402 401
403 402 # include RhodeCode plugins
404 403 includes = aslist(settings.get('rhodecode.includes', []))
405 404 log.debug('processing rhodecode.includes data...')
406 405 for inc in includes:
407 406 config.include(inc)
408 407
409 408 # custom not found view, if our pyramid app doesn't know how to handle
410 409 # the request pass it to potential VCS handling ap
411 410 config.add_notfound_view(not_found_view)
412 411 if not settings.get('debugtoolbar.enabled', False):
413 412 # disabled debugtoolbar handle all exceptions via the error_handlers
414 413 config.add_view(error_handler, context=Exception)
415 414
416 415 # all errors including 403/404/50X
417 416 config.add_view(error_handler, context=HTTPError)
418 417
419 418
420 419 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
421 420 """
422 421 Apply outer WSGI middlewares around the application.
423 422 """
424 423 registry = config.registry
425 424 settings = registry.settings
426 425
427 426 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
428 427 pyramid_app = HttpsFixup(pyramid_app, settings)
429 428
430 429 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
431 430 pyramid_app, settings)
432 431 registry.ae_client = _ae_client
433 432
434 433 if settings['gzip_responses']:
435 434 pyramid_app = make_gzip_middleware(
436 435 pyramid_app, settings, compress_level=1)
437 436
438 437 # this should be the outer most middleware in the wsgi stack since
439 438 # middleware like Routes make database calls
440 439 def pyramid_app_with_cleanup(environ, start_response):
441 440 start = time.time()
442 441 try:
443 442 return pyramid_app(environ, start_response)
444 443 finally:
445 444 # Dispose current database session and rollback uncommitted
446 445 # transactions.
447 446 meta.Session.remove()
448 447
449 448 # In a single threaded mode server, on non sqlite db we should have
450 449 # '0 Current Checked out connections' at the end of a request,
451 450 # if not, then something, somewhere is leaving a connection open
452 451 pool = meta.Base.metadata.bind.engine.pool
453 452 log.debug('sa pool status: %s', pool.status())
454 453 total = time.time() - start
455 454 log.debug('Request processing finalized: %.4fs', total)
456 455
457 456 return pyramid_app_with_cleanup
458 457
459 458
460 459 def sanitize_settings_and_apply_defaults(global_config, settings):
461 460 """
462 461 Applies settings defaults and does all type conversion.
463 462
464 463 We would move all settings parsing and preparation into this place, so that
465 464 we have only one place left which deals with this part. The remaining parts
466 465 of the application would start to rely fully on well prepared settings.
467 466
468 467 This piece would later be split up per topic to avoid a big fat monster
469 468 function.
470 469 """
471 470
472 471 global_settings_maker = SettingsMaker(global_config)
473 472 global_settings_maker.make_setting('debug', default=False, parser='bool')
474 473 debug_enabled = asbool(global_config.get('debug'))
475 474
476 475 settings_maker = SettingsMaker(settings)
477 476
478 477 settings_maker.make_setting(
479 478 'logging.autoconfigure',
480 479 default=False,
481 480 parser='bool')
482 481
483 482 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
484 483 settings_maker.enable_logging(logging_conf, level='INFO' if debug_enabled else 'DEBUG')
485 484
486 485 # Default includes, possible to change as a user
487 486 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
488 487 log.debug(
489 488 "Using the following pyramid.includes: %s",
490 489 pyramid_includes)
491 490
492 491 settings_maker.make_setting('rhodecode.edition', 'Community Edition')
493 492 settings_maker.make_setting('rhodecode.edition_id', 'CE')
494 493
495 494 if 'mako.default_filters' not in settings:
496 495 # set custom default filters if we don't have it defined
497 496 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
498 497 settings['mako.default_filters'] = 'h_filter'
499 498
500 499 if 'mako.directories' not in settings:
501 500 mako_directories = settings.setdefault('mako.directories', [
502 501 # Base templates of the original application
503 502 'rhodecode:templates',
504 503 ])
505 504 log.debug(
506 505 "Using the following Mako template directories: %s",
507 506 mako_directories)
508 507
509 508 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
510 509 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
511 510 raw_url = settings['beaker.session.url']
512 511 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
513 512 settings['beaker.session.url'] = 'redis://' + raw_url
514 513
515 514 settings_maker.make_setting('__file__', global_config.get('__file__'))
516 515
517 516 # TODO: johbo: Re-think this, usually the call to config.include
518 517 # should allow to pass in a prefix.
519 518 settings_maker.make_setting('rhodecode.api.url', '/_admin/api')
520 519
521 520 # Sanitize generic settings.
522 521 settings_maker.make_setting('default_encoding', 'UTF-8', parser='list')
523 522 settings_maker.make_setting('is_test', False, parser='bool')
524 523 settings_maker.make_setting('gzip_responses', False, parser='bool')
525 524
526 525 # statsd
527 526 settings_maker.make_setting('statsd.enabled', False, parser='bool')
528 527 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
529 528 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
530 529 settings_maker.make_setting('statsd.statsd_prefix', '')
531 530 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
532 531
533 532 settings_maker.make_setting('vcs.svn.compatible_version', '')
534 533 settings_maker.make_setting('vcs.hooks.protocol', 'http')
535 534 settings_maker.make_setting('vcs.hooks.host', '127.0.0.1')
536 535 settings_maker.make_setting('vcs.scm_app_implementation', 'http')
537 536 settings_maker.make_setting('vcs.server', '')
538 537 settings_maker.make_setting('vcs.server.protocol', 'http')
539 538 settings_maker.make_setting('vcs.server.enable', 'true', parser='bool')
540 539 settings_maker.make_setting('startup.import_repos', 'false', parser='bool')
541 540 settings_maker.make_setting('vcs.hooks.direct_calls', 'false', parser='bool')
542 541 settings_maker.make_setting('vcs.start_server', 'false', parser='bool')
543 542 settings_maker.make_setting('vcs.backends', 'hg, git, svn', parser='list')
544 543 settings_maker.make_setting('vcs.connection_timeout', 3600, parser='int')
545 544
546 545 settings_maker.make_setting('vcs.methods.cache', True, parser='bool')
547 546
548 547 # Support legacy values of vcs.scm_app_implementation. Legacy
549 548 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
550 549 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
551 550 scm_app_impl = settings['vcs.scm_app_implementation']
552 551 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
553 552 settings['vcs.scm_app_implementation'] = 'http'
554 553
555 554 settings_maker.make_setting('appenlight', False, parser='bool')
556 555
557 556 temp_store = tempfile.gettempdir()
558 557 tmp_cache_dir = os.path.join(temp_store, 'rc_cache')
559 558
560 559 # save default, cache dir, and use it for all backends later.
561 560 default_cache_dir = settings_maker.make_setting(
562 561 'cache_dir',
563 562 default=tmp_cache_dir, default_when_empty=True,
564 563 parser='dir:ensured')
565 564
566 565 # exception store cache
567 566 settings_maker.make_setting(
568 567 'exception_tracker.store_path',
569 568 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
570 569 parser='dir:ensured'
571 570 )
572 571
573 572 settings_maker.make_setting(
574 573 'celerybeat-schedule.path',
575 574 default=os.path.join(default_cache_dir, 'celerybeat_schedule', 'celerybeat-schedule.db'), default_when_empty=True,
576 575 parser='file:ensured'
577 576 )
578 577
579 578 settings_maker.make_setting('exception_tracker.send_email', False, parser='bool')
580 579 settings_maker.make_setting('exception_tracker.email_prefix', '[RHODECODE ERROR]', default_when_empty=True)
581 580
582 581 # cache_general
583 582 settings_maker.make_setting('rc_cache.cache_general.backend', 'dogpile.cache.rc.file_namespace')
584 583 settings_maker.make_setting('rc_cache.cache_general.expiration_time', 60 * 60 * 12, parser='int')
585 584 settings_maker.make_setting('rc_cache.cache_general.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_general.db'))
586 585
587 586 # cache_perms
588 587 settings_maker.make_setting('rc_cache.cache_perms.backend', 'dogpile.cache.rc.file_namespace')
589 588 settings_maker.make_setting('rc_cache.cache_perms.expiration_time', 60 * 60, parser='int')
590 589 settings_maker.make_setting('rc_cache.cache_perms.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_perms.db'))
591 590
592 591 # cache_repo
593 592 settings_maker.make_setting('rc_cache.cache_repo.backend', 'dogpile.cache.rc.file_namespace')
594 593 settings_maker.make_setting('rc_cache.cache_repo.expiration_time', 60 * 60 * 24 * 30, parser='int')
595 594 settings_maker.make_setting('rc_cache.cache_repo.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_repo.db'))
596 595
597 596 # cache_license
598 597 settings_maker.make_setting('rc_cache.cache_license.backend', 'dogpile.cache.rc.file_namespace')
599 598 settings_maker.make_setting('rc_cache.cache_license.expiration_time', 60 * 5, parser='int')
600 599 settings_maker.make_setting('rc_cache.cache_license.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_license.db'))
601 600
602 601 # cache_repo_longterm memory, 96H
603 602 settings_maker.make_setting('rc_cache.cache_repo_longterm.backend', 'dogpile.cache.rc.memory_lru')
604 603 settings_maker.make_setting('rc_cache.cache_repo_longterm.expiration_time', 345600, parser='int')
605 604 settings_maker.make_setting('rc_cache.cache_repo_longterm.max_size', 10000, parser='int')
606 605
607 606 # sql_cache_short
608 607 settings_maker.make_setting('rc_cache.sql_cache_short.backend', 'dogpile.cache.rc.memory_lru')
609 608 settings_maker.make_setting('rc_cache.sql_cache_short.expiration_time', 30, parser='int')
610 609 settings_maker.make_setting('rc_cache.sql_cache_short.max_size', 10000, parser='int')
611 610
612 611 settings_maker.env_expand()
613 612
614 613 # configure instance id
615 614 config_utils.set_instance_id(settings)
616 615
617 616 return settings
@@ -1,99 +1,99 b''
1 # -*- coding: utf-8 -*-
1
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Compatibility patches.
23 23
24 24 Please keep the following principles in mind:
25 25
26 26 * Keep imports local, so that importing this module does not cause too many
27 27 side effects by itself.
28 28
29 29 * Try to make patches idempotent, calling them multiple times should not do
30 30 harm. If that is not possible, ensure that the second call explodes.
31 31
32 32 """
33 33
34 34
35 35 def inspect_getargspec():
36 36 """
37 37 Pyramid rely on inspect.getargspec to lookup the signature of
38 38 view functions. This is not compatible with cython, therefore we replace
39 39 getargspec with a custom version.
40 40 Code is inspired by the inspect module from Python-3.4
41 41 """
42 42 import inspect
43 43
44 44 def _isCython(func):
45 45 """
46 46 Private helper that checks if a function is a cython function.
47 47 """
48 48 return func.__class__.__name__ == 'cython_function_or_method'
49 49
50 50 def unwrap(func):
51 51 """
52 52 Get the object wrapped by *func*.
53 53
54 54 Follows the chain of :attr:`__wrapped__` attributes returning the last
55 55 object in the chain.
56 56
57 57 *stop* is an optional callback accepting an object in the wrapper chain
58 58 as its sole argument that allows the unwrapping to be terminated early
59 59 if the callback returns a true value. If the callback never returns a
60 60 true value, the last object in the chain is returned as usual. For
61 61 example, :func:`signature` uses this to stop unwrapping if any object
62 62 in the chain has a ``__signature__`` attribute defined.
63 63
64 64 :exc:`ValueError` is raised if a cycle is encountered.
65 65 """
66 66 f = func # remember the original func for error reporting
67 67 memo = {id(f)} # Memoise by id to tolerate non-hashable objects
68 68 while hasattr(func, '__wrapped__'):
69 69 func = func.__wrapped__
70 70 id_func = id(func)
71 71 if id_func in memo:
72 72 raise ValueError('wrapper loop when unwrapping {!r}'.format(f))
73 73 memo.add(id_func)
74 74 return func
75 75
76 76 def custom_getargspec(func):
77 77 """
78 78 Get the names and default values of a function's arguments.
79 79
80 80 A tuple of four things is returned: (args, varargs, varkw, defaults).
81 81 'args' is a list of the argument names (it may contain nested lists).
82 82 'varargs' and 'varkw' are the names of the * and ** arguments or None.
83 83 'defaults' is an n-tuple of the default values of the last n arguments.
84 84 """
85 85
86 86 func = unwrap(func)
87 87
88 88 if inspect.ismethod(func):
89 89 func = func.im_func
90 90 if not inspect.isfunction(func):
91 91 if not _isCython(func):
92 92 raise TypeError('{!r} is not a Python or Cython function'
93 93 .format(func))
94 94 args, varargs, varkw = inspect.getargs(func.func_code)
95 95 return inspect.ArgSpec(args, varargs, varkw, func.func_defaults)
96 96
97 97 inspect.getargspec = custom_getargspec
98 98
99 99 return inspect
@@ -1,88 +1,88 b''
1 # -*- coding: utf-8 -*-
1
2 2 # Copyright (C) 2016-2020 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 """
21 21 example usage in hooks::
22 22
23 23 from .helpers import extra_fields
24 24 # returns list of dicts with key-val fetched from extra fields
25 25 repo_extra_fields = extra_fields.run(**kwargs)
26 26 repo_extra_fields.get('endpoint_url')
27 27
28 28 # the field stored the following example values
29 29 {u'created_on': datetime.datetime(),
30 30 u'field_key': u'endpoint_url',
31 31 u'field_label': u'Endpoint URL',
32 32 u'field_desc': u'Full HTTP endpoint to call if given',
33 33 u'field_type': u'str',
34 34 u'field_value': u'http://server.com/post',
35 35 u'repo_field_id': 1,
36 36 u'repository_id': 1}
37 37 # for example to obtain the value:
38 38 endpoint_field = repo_extra_fields.get('endpoint_url')
39 39 if endpoint_field:
40 40 url = endpoint_field['field_value']
41 41
42 42 """
43 43
44 44
45 45 def run(*args, **kwargs):
46 46 from rhodecode.model.db import Repository
47 47 # use temp name then the main one propagated
48 48 repo_name = kwargs.pop('REPOSITORY', None) or kwargs['repository']
49 49 repo = Repository.get_by_repo_name(repo_name)
50 50
51 51 fields = {}
52 52 for field in repo.extra_fields:
53 53 fields[field.field_key] = field.get_dict()
54 54
55 55 return fields
56 56
57 57
58 58 class _Undefined(object):
59 59 pass
60 60
61 61
62 62 def get_field(extra_fields_data, key, default=_Undefined(), convert_type=True):
63 63 """
64 64 field_value = get_field(extra_fields, key='ci_endpoint_url', default='')
65 65 """
66 66 from ..utils import str2bool, aslist
67 67
68 68 if key not in extra_fields_data:
69 69 if isinstance(default, _Undefined):
70 70 raise ValueError('key {} not present in extra_fields'.format(key))
71 71 return default
72 72
73 73 # NOTE(dan): from metadata we get field_label, field_value, field_desc, field_type
74 74 field_metadata = extra_fields_data[key]
75 75
76 76 field_value = field_metadata['field_value']
77 77
78 78 # NOTE(dan): empty value, use default
79 79 if not field_value and not isinstance(default, _Undefined):
80 80 return default
81 81
82 82 if convert_type:
83 83 # 'str', 'unicode', 'list', 'tuple'
84 84 _type = field_metadata['field_type']
85 85 if _type in ['list', 'tuple']:
86 86 field_value = aslist(field_value)
87 87
88 88 return field_value
@@ -1,61 +1,61 b''
1 # -*- coding: utf-8 -*-
1
2 2 # Copyright (C) 2016-2020 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 """
21 21 Extract and serialize commits taken from a list of commit_ids. This should
22 22 be used in post_push hook
23 23
24 24 us in hooks::
25 25
26 26 from .helpers import extract_post_commits
27 27 # returns list of dicts with key-val fetched from extra fields
28 28 commit_list = extract_post_commits.run(**kwargs)
29 29 """
30 30 import traceback
31 31
32 32
33 33 def run(*args, **kwargs):
34 34 from rhodecode.lib.utils2 import extract_mentioned_users
35 35 from rhodecode.model.db import Repository
36 36
37 37 commit_ids = kwargs.get('commit_ids')
38 38 if not commit_ids:
39 39 return 0
40 40
41 41 # use temp name then the main one propagated
42 42 repo_name = kwargs.pop('REPOSITORY', None) or kwargs['repository']
43 43
44 44 repo = Repository.get_by_repo_name(repo_name)
45 45 commits = []
46 46
47 47 vcs_repo = repo.scm_instance(cache=False)
48 48 try:
49 49 for commit_id in commit_ids:
50 50 cs = vcs_repo.get_changeset(commit_id)
51 51 cs_data = cs.__json__()
52 52 cs_data['mentions'] = extract_mentioned_users(cs_data['message'])
53 53 # optionally add more logic to parse the commits, like reading extra
54 54 # fields of repository to read managers of reviewers ?
55 55 commits.append(cs_data)
56 56 except Exception:
57 57 print(traceback.format_exc())
58 58 # we don't send any commits when crash happens, only full list matters
59 59 # we short circuit then.
60 60 return []
61 61 return commits
@@ -1,91 +1,91 b''
1 # -*- coding: utf-8 -*-
1
2 2 # Copyright (C) 2016-2020 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 """
21 21 us in hooks::
22 22
23 23 from .helpers import extract_pre_commits
24 24 # returns list of dicts with key-val fetched from extra fields
25 25 commit_list = extract_pre_commits.run(**kwargs)
26 26
27 27 """
28 28 import re
29 29 import collections
30 30 import json
31 31
32 32
33 33 def get_hg_commits(repo, refs):
34 34 commits = []
35 35 return commits
36 36
37 37
38 38 def get_git_commits(repo, refs):
39 39 commits = []
40 40
41 41 for data in refs:
42 42 # we should now extract commit data
43 43 old_rev = data['old_rev']
44 44 new_rev = data['new_rev']
45 45
46 46 if '00000000' in old_rev:
47 47 # new branch, we don't need to extract nothing
48 48 return commits
49 49
50 50 git_env = dict(data['git_env'])
51 51 # https://github.com/git/git/blob/master/Documentation/pretty-formats.txt
52 52 cmd = [
53 53 'log',
54 54 '--pretty=format:{"commit_id": "%H", "author": "%aN <%aE>", "date": "%ad", "message": "%s"}',
55 55 '{}...{}'.format(old_rev, new_rev)
56 56 ]
57 57
58 58 stdout, stderr = repo.run_git_command(cmd, extra_env=git_env)
59 59 for line in stdout.splitlines():
60 60 try:
61 61 data = json.loads(line)
62 62 commits.append(data)
63 63 except Exception:
64 64 print('Failed to load data from GIT line')
65 65
66 66 return commits
67 67
68 68
69 69 def run(*args, **kwargs):
70 70 from rhodecode.model.db import Repository
71 71
72 72 vcs_type = kwargs['scm']
73 73 # use temp name then the main one propagated
74 74 repo_name = kwargs.pop('REPOSITORY', None) or kwargs['repository']
75 75
76 76 repo = Repository.get_by_repo_name(repo_name)
77 77 vcs_repo = repo.scm_instance(cache=False)
78 78
79 79 commits = []
80 80
81 81 if vcs_type == 'git':
82 82 for rev_data in kwargs['commit_ids']:
83 83 new_environ = dict((k, v) for k, v in rev_data['git_env'])
84 84 commits = get_git_commits(vcs_repo, kwargs['commit_ids'])
85 85
86 86 if vcs_type == 'hg':
87 87 for rev_data in kwargs['commit_ids']:
88 88 new_environ = dict((k, v) for k, v in rev_data['hg_env'])
89 89 commits = get_hg_commits(vcs_repo, kwargs['commit_ids'])
90 90
91 91 return commits
@@ -1,147 +1,147 b''
1 # -*- coding: utf-8 -*-
1
2 2 # Copyright (C) 2016-2020 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 """
21 21 us in hooks::
22 22
23 23 from .helpers import extract_pre_files
24 24 # returns list of dicts with key-val fetched from extra fields
25 25 file_list = extract_pre_files.run(**kwargs)
26 26
27 27 """
28 28 import re
29 29 import collections
30 30 import json
31 31
32 32 from rhodecode.lib import diffs
33 33 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
34 34 from rhodecode.lib.vcs.backends.git.diff import GitDiff
35 35 from vcsserver.utils import safe_int
36 36
37 37
38 38 def get_svn_files(repo, vcs_repo, refs):
39 39 txn_id = refs[0]
40 40 files = []
41 41
42 42 stdout, stderr = vcs_repo.run_svn_command(
43 43 ['svnlook', 'changed', repo.repo_full_path, '--transaction', txn_id])
44 44
45 45 svn_op_to_rc_op = {
46 46 'A': 'A',
47 47 'U': 'M',
48 48 'D': 'D',
49 49 }
50 50
51 51 for entry in stdout.splitlines():
52 52 parsed_entry = {
53 53 'raw_diff': '',
54 54 'filename': '',
55 55 'chunks': [],
56 56 'ops': {},
57 57 'file_size': 0
58 58 }
59 59
60 60 op = entry[0]
61 61 path = entry[1:].strip()
62 62
63 63 rc_op = svn_op_to_rc_op.get(op) or '?'
64 64 parsed_entry['filename'] = path
65 65 parsed_entry['operation'] = rc_op
66 66
67 67 if rc_op in ['A', 'M']:
68 68
69 69 stdout, stderr = vcs_repo.run_svn_command(
70 70 ['svnlook', 'filesize', repo.repo_full_path, path, '--transaction', txn_id],
71 71 _safe=True
72 72 )
73 73
74 74 if "Path '{}' is not a file".format(path.rstrip('/')) in stderr:
75 75 # skip dirs
76 76 continue
77 77
78 78 parsed_entry['file_size'] = safe_int(stdout.strip()) or 0
79 79
80 80 files.append(parsed_entry)
81 81
82 82 return files
83 83
84 84
85 85 def get_hg_files(repo, vcs_repo, refs):
86 86 files = []
87 87 return files
88 88
89 89
90 90 def get_git_files(repo, vcs_repo, refs):
91 91 files = []
92 92
93 93 for data in refs:
94 94 # we should now extract commit data
95 95 old_rev = data['old_rev']
96 96 new_rev = data['new_rev']
97 97
98 98 if '00000000' in old_rev:
99 99 # new branch, we don't need to extract nothing
100 100 return files
101 101
102 102 git_env = dict(data['git_env'])
103 103
104 104 cmd = [
105 105 'diff', old_rev, new_rev
106 106 ]
107 107
108 108 stdout, stderr = vcs_repo.run_git_command(cmd, extra_env=git_env)
109 109 vcs_diff = GitDiff(stdout)
110 110
111 111 diff_processor = diffs.DiffProcessor(vcs_diff, format='newdiff')
112 112 # this is list of dicts with diff information
113 113 # _parsed[0].keys()
114 114 # ['raw_diff', 'old_revision', 'stats', 'original_filename',
115 115 # 'is_limited_diff', 'chunks', 'new_revision', 'operation',
116 116 # 'exceeds_limit', 'filename']
117 117 files = _parsed = diff_processor.prepare()
118 118
119 119 return files
120 120
121 121
122 122 def run(*args, **kwargs):
123 123 from rhodecode.model.db import Repository
124 124
125 125 vcs_type = kwargs['scm']
126 126 # use temp name then the main one propagated
127 127 repo_name = kwargs.pop('REPOSITORY', None) or kwargs['repository']
128 128
129 129 repo = Repository.get_by_repo_name(repo_name)
130 130 vcs_repo = repo.scm_instance(cache=False)
131 131
132 132 files = []
133 133
134 134 if vcs_type == 'git':
135 135 for rev_data in kwargs['commit_ids']:
136 136 new_environ = dict((k, v) for k, v in rev_data['git_env'])
137 137 files = get_git_files(repo, vcs_repo, kwargs['commit_ids'])
138 138
139 139 if vcs_type == 'hg':
140 140 for rev_data in kwargs['commit_ids']:
141 141 new_environ = dict((k, v) for k, v in rev_data['hg_env'])
142 142 files = get_hg_files(repo, vcs_repo, kwargs['commit_ids'])
143 143
144 144 if vcs_type == 'svn':
145 145 files = get_svn_files(repo, vcs_repo, kwargs['commit_ids'])
146 146
147 147 return files
@@ -1,49 +1,49 b''
1 # -*- coding: utf-8 -*-
1
2 2 # Copyright (C) 2016-2020 RhodeCode GmbH
3 3 #
4 4 # This program is free software: you can redistribute it and/or modify
5 5 # it under the terms of the GNU Affero General Public License, version 3
6 6 # (only), as published by the Free Software Foundation.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU Affero General Public License
14 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 15 #
16 16 # This program is dual-licensed. If you wish to learn more about the
17 17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 19
20 20 """
21 21 us in hooks::
22 22
23 23 from .helpers import http_call
24 24 # returns response after making a POST call
25 25 response = http_call.run(url=url, json_data={"key": "val"})
26 26
27 27 # returns response after making a GET call
28 28 response = http_call.run(url=url, params={"key": "val"}, method='get')
29 29
30 30 """
31 31
32 32 from rhodecode.integrations.types.base import requests_retry_call
33 33
34 34
35 35 def run(url, json_data=None, params=None, method='post'):
36 36 requests_session = requests_retry_call()
37 37 requests_session.verify = True # Verify SSL
38 38 method_caller = getattr(requests_session, method, 'post')
39 39
40 40 timeout = 60
41 41 if json_data:
42 42 resp = method_caller(url, json=json_data, timeout=timeout)
43 43 elif params:
44 44 resp = method_caller(url, params=params, timeout=timeout)
45 45 else:
46 46 raise AttributeError('Provide json_data= or params= in function call')
47 47 resp.raise_for_status() # raise exception on a failed request
48 48 return resp
49 49
@@ -1,106 +1,105 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 """
22 21 Single source for redirection links.
23 22
24 23 Goal of this module is to provide a single source of truth regarding external
25 24 links. The data inside this module is used to configure the routing
26 25 system of Enterprise and it is used also as a base to check if this data
27 26 and our server configuration are in sync.
28 27
29 28 .. py:data:: link_config
30 29
31 30 Contains the configuration for external links. Each item is supposed to be
32 31 a `dict` like this example::
33 32
34 33 {"name": "url_name",
35 34 "target": "https://rhodecode.com/r1/enterprise/keyword/",
36 35 "external_target": "https://example.com/some-page.html",
37 36 }
38 37
39 38 then you can retrieve the url by simply calling the URL function:
40 39
41 40 `h.route_path('url_name')`
42 41
43 42 The redirection must be first implemented in our servers before
44 43 you can see it working.
45 44 """
46 45 # pragma: no cover
47 46
48 47
49 48 link_config = [
50 49 {
51 50 "name": "enterprise_docs",
52 51 "target": "https://rhodecode.com/r1/enterprise/docs/",
53 52 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/",
54 53 },
55 54 {
56 55 "name": "enterprise_log_file_locations",
57 56 "target": "https://rhodecode.com/r1/enterprise/docs/admin-system-overview/",
58 57 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/system-overview.html#log-files",
59 58 },
60 59 {
61 60 "name": "enterprise_issue_tracker_settings",
62 61 "target": "https://rhodecode.com/r1/enterprise/docs/issue-trackers-overview/",
63 62 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/issue-trackers/issue-trackers.html",
64 63 },
65 64 {
66 65 "name": "enterprise_svn_setup",
67 66 "target": "https://rhodecode.com/r1/enterprise/docs/svn-setup/",
68 67 "external_target": "https://docs.rhodecode.com/RhodeCode-Enterprise/admin/svn-http.html",
69 68 },
70 69 {
71 70 "name": "enterprise_license_convert_from_old",
72 71 "target": "https://rhodecode.com/r1/enterprise/convert-license/",
73 72 "external_target": "https://rhodecode.com/u/license-upgrade",
74 73 },
75 74 {
76 75 "name": "rst_help",
77 76 "target": "http://docutils.sourceforge.io/docs/user/rst/quickref.html",
78 77 "external_target": "https://docutils.sourceforge.io/docs/user/rst/quickref.html",
79 78 },
80 79 {
81 80 "name": "markdown_help",
82 81 "target": "https://daringfireball.net/projects/markdown/syntax",
83 82 "external_target": "https://daringfireball.net/projects/markdown/syntax",
84 83 },
85 84 {
86 85 "name": "rhodecode_official",
87 86 "target": "https://rhodecode.com",
88 87 "external_target": "https://rhodecode.com/",
89 88 },
90 89 {
91 90 "name": "rhodecode_support",
92 91 "target": "https://rhodecode.com/help/",
93 92 "external_target": "https://rhodecode.com/support",
94 93 },
95 94 {
96 95 "name": "rhodecode_translations",
97 96 "target": "https://rhodecode.com/translate/enterprise",
98 97 "external_target": "https://explore.transifex.com/rhodecode/RhodeCode/",
99 98 },
100 99
101 100 ]
102 101
103 102
104 103 def connect_redirection_links(config):
105 104 for link in link_config:
106 105 config.add_route(link['name'], link['target'], static=True)
@@ -1,205 +1,204 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 import os
22 21 import textwrap
23 22 import string
24 23 import functools
25 24 import logging
26 25 import tempfile
27 26 import logging.config
28 27 log = logging.getLogger(__name__)
29 28
30 29 # skip keys, that are set here, so we don't double process those
31 30 set_keys = {
32 31 '__file__': ''
33 32 }
34 33
35 34
36 35 def str2bool(_str):
37 36 """
38 37 returns True/False value from given string, it tries to translate the
39 38 string into boolean
40 39
41 40 :param _str: string value to translate into boolean
42 41 :rtype: boolean
43 42 :returns: boolean from given string
44 43 """
45 44 if _str is None:
46 45 return False
47 46 if _str in (True, False):
48 47 return _str
49 48 _str = str(_str).strip().lower()
50 49 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
51 50
52 51
53 52 def aslist(obj, sep=None, strip=True):
54 53 """
55 54 Returns given string separated by sep as list
56 55
57 56 :param obj:
58 57 :param sep:
59 58 :param strip:
60 59 """
61 60 if isinstance(obj, str):
62 61 if obj in ['', ""]:
63 62 return []
64 63
65 64 lst = obj.split(sep)
66 65 if strip:
67 66 lst = [v.strip() for v in lst]
68 67 return lst
69 68 elif isinstance(obj, (list, tuple)):
70 69 return obj
71 70 elif obj is None:
72 71 return []
73 72 else:
74 73 return [obj]
75 74
76 75
77 76 class SettingsMaker(object):
78 77
79 78 def __init__(self, app_settings):
80 79 self.settings = app_settings
81 80
82 81 @classmethod
83 82 def _bool_func(cls, input_val):
84 83 return str2bool(input_val)
85 84
86 85 @classmethod
87 86 def _int_func(cls, input_val):
88 87 return int(input_val)
89 88
90 89 @classmethod
91 90 def _list_func(cls, input_val, sep=','):
92 91 return aslist(input_val, sep=sep)
93 92
94 93 @classmethod
95 94 def _string_func(cls, input_val, lower=True):
96 95 if lower:
97 96 input_val = input_val.lower()
98 97 return input_val
99 98
100 99 @classmethod
101 100 def _float_func(cls, input_val):
102 101 return float(input_val)
103 102
104 103 @classmethod
105 104 def _dir_func(cls, input_val, ensure_dir=False, mode=0o755):
106 105
107 106 # ensure we have our dir created
108 107 if not os.path.isdir(input_val) and ensure_dir:
109 108 os.makedirs(input_val, mode=mode)
110 109
111 110 if not os.path.isdir(input_val):
112 111 raise Exception('Dir at {} does not exist'.format(input_val))
113 112 return input_val
114 113
115 114 @classmethod
116 115 def _file_path_func(cls, input_val, ensure_dir=False, mode=0o755):
117 116 dirname = os.path.dirname(input_val)
118 117 cls._dir_func(dirname, ensure_dir=ensure_dir)
119 118 return input_val
120 119
121 120 @classmethod
122 121 def _key_transformator(cls, key):
123 122 return "{}_{}".format('RC'.upper(), key.upper().replace('.', '_').replace('-', '_'))
124 123
125 124 def maybe_env_key(self, key):
126 125 # now maybe we have this KEY in env, search and use the value with higher priority.
127 126 transformed_key = self._key_transformator(key)
128 127 envvar_value = os.environ.get(transformed_key)
129 128 if envvar_value:
130 129 log.debug('using `%s` key instead of `%s` key for config', transformed_key, key)
131 130
132 131 return envvar_value
133 132
134 133 def env_expand(self):
135 134 replaced = {}
136 135 for k, v in self.settings.items():
137 136 if k not in set_keys:
138 137 envvar_value = self.maybe_env_key(k)
139 138 if envvar_value:
140 139 replaced[k] = envvar_value
141 140 set_keys[k] = envvar_value
142 141
143 142 # replace ALL keys updated
144 143 self.settings.update(replaced)
145 144
146 145 def enable_logging(self, logging_conf=None, level='INFO', formatter='generic'):
147 146 """
148 147 Helper to enable debug on running instance
149 148 :return:
150 149 """
151 150
152 151 if not str2bool(self.settings.get('logging.autoconfigure')):
153 152 log.info('logging configuration based on main .ini file')
154 153 return
155 154
156 155 if logging_conf is None:
157 156 logging_conf = self.settings.get('logging.logging_conf_file') or ''
158 157
159 158 if not os.path.isfile(logging_conf):
160 159 log.error('Unable to setup logging based on %s, '
161 160 'file does not exist.... specify path using logging.logging_conf_file= config setting. ', logging_conf)
162 161 return
163 162
164 163 with open(logging_conf, 'rb') as f:
165 164 ini_template = textwrap.dedent(f.read())
166 165 ini_template = string.Template(ini_template).safe_substitute(
167 166 RC_LOGGING_LEVEL=os.environ.get('RC_LOGGING_LEVEL', '') or level,
168 167 RC_LOGGING_FORMATTER=os.environ.get('RC_LOGGING_FORMATTER', '') or formatter
169 168 )
170 169
171 170 with tempfile.NamedTemporaryFile(prefix='rc_logging_', suffix='.ini', delete=False) as f:
172 171 log.info('Saved Temporary LOGGING config at %s', f.name)
173 172 f.write(ini_template)
174 173
175 174 logging.config.fileConfig(f.name)
176 175 os.remove(f.name)
177 176
178 177 def make_setting(self, key, default, lower=False, default_when_empty=False, parser=None):
179 178 input_val = self.settings.get(key, default)
180 179
181 180 if default_when_empty and not input_val:
182 181 # use default value when value is set in the config but it is empty
183 182 input_val = default
184 183
185 184 parser_func = {
186 185 'bool': self._bool_func,
187 186 'int': self._int_func,
188 187 'list': self._list_func,
189 188 'list:newline': functools.partial(self._list_func, sep='/n'),
190 189 'list:spacesep': functools.partial(self._list_func, sep=' '),
191 190 'string': functools.partial(self._string_func, lower=lower),
192 191 'dir': self._dir_func,
193 192 'dir:ensured': functools.partial(self._dir_func, ensure_dir=True),
194 193 'file': self._file_path_func,
195 194 'file:ensured': functools.partial(self._file_path_func, ensure_dir=True),
196 195 None: lambda i: i
197 196 }[parser]
198 197
199 198 envvar_value = self.maybe_env_key(key)
200 199 if envvar_value:
201 200 input_val = envvar_value
202 201 set_keys[key] = input_val
203 202
204 203 self.settings[key] = parser_func(input_val)
205 204 return self.settings[key]
@@ -1,104 +1,103 b''
1 # -*- coding: utf-8 -*-
2 1
3 2 # Copyright (C) 2010-2020 RhodeCode GmbH
4 3 #
5 4 # This program is free software: you can redistribute it and/or modify
6 5 # it under the terms of the GNU Affero General Public License, version 3
7 6 # (only), as published by the Free Software Foundation.
8 7 #
9 8 # This program is distributed in the hope that it will be useful,
10 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 11 # GNU General Public License for more details.
13 12 #
14 13 # You should have received a copy of the GNU Affero General Public License
15 14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 15 #
17 16 # This program is dual-licensed. If you wish to learn more about the
18 17 # RhodeCode Enterprise Edition, including its added features, Support services,
19 18 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 19
21 20 import os
22 21 import platform
23 22
24 23 from rhodecode.model import init_model
25 24
26 25
27 26 def configure_vcs(config):
28 27 """
29 28 Patch VCS config with some RhodeCode specific stuff
30 29 """
31 30 from rhodecode.lib.vcs import conf
32 31 import rhodecode.lib.vcs.conf.settings
33 32
34 33 conf.settings.BACKENDS = {
35 34 'hg': 'rhodecode.lib.vcs.backends.hg.MercurialRepository',
36 35 'git': 'rhodecode.lib.vcs.backends.git.GitRepository',
37 36 'svn': 'rhodecode.lib.vcs.backends.svn.SubversionRepository',
38 37 }
39 38
40 39 conf.settings.HOOKS_PROTOCOL = config['vcs.hooks.protocol']
41 40 conf.settings.HOOKS_HOST = config['vcs.hooks.host']
42 41 conf.settings.HOOKS_DIRECT_CALLS = config['vcs.hooks.direct_calls']
43 42 conf.settings.DEFAULT_ENCODINGS = config['default_encoding']
44 43 conf.settings.ALIASES[:] = config['vcs.backends']
45 44 conf.settings.SVN_COMPATIBLE_VERSION = config['vcs.svn.compatible_version']
46 45
47 46
48 47 def initialize_database(config):
49 48 from rhodecode.lib.utils2 import engine_from_config, get_encryption_key
50 49 engine = engine_from_config(config, 'sqlalchemy.db1.')
51 50 init_model(engine, encryption_key=get_encryption_key(config))
52 51
53 52
54 53 def initialize_test_environment(settings, test_env=None):
55 54 if test_env is None:
56 55 test_env = not int(os.environ.get('RC_NO_TMP_PATH', 0))
57 56
58 57 from rhodecode.lib.utils import (
59 58 create_test_directory, create_test_database, create_test_repositories,
60 59 create_test_index)
61 60 from rhodecode.tests import TESTS_TMP_PATH
62 61 from rhodecode.lib.vcs.backends.hg import largefiles_store
63 62 from rhodecode.lib.vcs.backends.git import lfs_store
64 63
65 64 # test repos
66 65 if test_env:
67 66 create_test_directory(TESTS_TMP_PATH)
68 67 # large object stores
69 68 create_test_directory(largefiles_store(TESTS_TMP_PATH))
70 69 create_test_directory(lfs_store(TESTS_TMP_PATH))
71 70
72 71 create_test_database(TESTS_TMP_PATH, settings)
73 72 create_test_repositories(TESTS_TMP_PATH, settings)
74 73 create_test_index(TESTS_TMP_PATH, settings)
75 74
76 75
77 76 def get_vcs_server_protocol(config):
78 77 return config['vcs.server.protocol']
79 78
80 79
81 80 def set_instance_id(config):
82 81 """
83 82 Sets a dynamic generated config['instance_id'] if missing or '*'
84 83 E.g instance_id = *cluster-1 or instance_id = *
85 84 """
86 85
87 86 config['instance_id'] = config.get('instance_id') or ''
88 87 instance_id = config['instance_id']
89 88 if instance_id.startswith('*') or not instance_id:
90 89 prefix = instance_id.lstrip('*')
91 90 _platform_id = platform.uname()[1] or 'instance'
92 91 config['instance_id'] = '{prefix}uname:{platform}-pid:{pid}'.format(
93 92 prefix=prefix,
94 93 platform=_platform_id,
95 94 pid=os.getpid())
96 95
97 96
98 97 def get_default_user_id():
99 98 from rhodecode.model.db import User, Session
100 99 user_id = Session()\
101 100 .query(User.user_id)\
102 101 .filter(User.username == User.DEFAULT_USER)\
103 102 .scalar()
104 103 return user_id
General Comments 0
You need to be logged in to leave comments. Login now