##// END OF EJS Templates
project: switched completly to pyproject.toml
super-admin -
r1101:e8c454cf python3
parent child Browse files
Show More
@@ -1,24 +1,51 b''
1 [build-system]
2 requires = ["setuptools>=61.0.0", "wheel"]
3 build-backend = "setuptools.build_meta"
4
1 [project]
5 [project]
2 name = "rhodecode-vcsserver"
6 name = "rhodecode-vcsserver"
3 description = ""
7 description = "Version Control System Server for RhodeCode"
8 authors = [
9 {name = "RhodeCode GmbH", email = "support@rhodecode.com"},
10 ]
11
12 license = {text = "GPL V3"}
4 requires-python = ">=3.10"
13 requires-python = ">=3.10"
5 version = "5.0.0"
14 dynamic = ["version", "readme"]
15 classifiers = [
16 'Development Status :: 6 - Mature',
17 'Intended Audience :: Developers',
18 'Operating System :: OS Independent',
19 'Topic :: Software Development :: Version Control',
20 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
21 'Programming Language :: Python :: 3.10',
22 ]
23
24 [project.entry-points."paste.app_factory"]
25 main = "vcsserver.http_main:main"
26
27 [tool.setuptools]
28 packages = ["vcsserver"]
29
30 [tool.setuptools.dynamic]
31 readme = {file = ["README.rst"], content-type = "text/rst"}
32 version = {file = "vcsserver/VERSION"}
6
33
7 [tool.ruff]
34 [tool.ruff]
8 select = [
35 select = [
9 # Pyflakes
36 # Pyflakes
10 "F",
37 "F",
11 # Pycodestyle
38 # Pycodestyle
12 "E",
39 "E",
13 "W",
40 "W",
14 # isort
41 # isort
15 "I001"
42 "I001"
16 ]
43 ]
17 ignore = [
44 ignore = [
18 "E501", # line too long, handled by black
45 "E501", # line too long, handled by black
19 ]
46 ]
20 # Same as Black.
47 # Same as Black.
21 line-length = 120
48 line-length = 120
22
49
23 [tool.ruff.isort]
50 [tool.ruff.isort]
24 known-first-party = ["vcsserver"]
51 known-first-party = ["vcsserver"]
@@ -1,28 +1,28 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2020 RhodeCode GmbH
2 # Copyright (C) 2014-2020 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
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 General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import pkgutil
18 import pkgutil
19
19
20
20
21 __version__ = pkgutil.get_data('vcsserver', 'VERSION').strip()
21 __version__ = pkgutil.get_data('vcsserver', 'VERSION').strip().decode()
22
22
23 # link to config for pyramid
23 # link to config for pyramid
24 CONFIG = {}
24 CONFIG = {}
25
25
26 # Populated with the settings dictionary from application init in
26 # Populated with the settings dictionary from application init in
27 #
27 #
28 PYRAMID_SETTINGS = {}
28 PYRAMID_SETTINGS = {}
@@ -1,770 +1,770 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2020 RhodeCode GmbH
2 # Copyright (C) 2014-2020 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
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 General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import io
18 import io
19 import os
19 import os
20 import sys
20 import sys
21 import base64
21 import base64
22 import locale
22 import locale
23 import logging
23 import logging
24 import uuid
24 import uuid
25 import time
25 import time
26 import wsgiref.util
26 import wsgiref.util
27 import traceback
27 import traceback
28 import tempfile
28 import tempfile
29 import psutil
29 import psutil
30
30
31 from itertools import chain
31 from itertools import chain
32
32
33 import msgpack
33 import msgpack
34 import configparser
34 import configparser
35
35
36 from pyramid.config import Configurator
36 from pyramid.config import Configurator
37 from pyramid.wsgi import wsgiapp
37 from pyramid.wsgi import wsgiapp
38 from pyramid.response import Response
38 from pyramid.response import Response
39
39
40 from vcsserver.base import BinaryEnvelope
40 from vcsserver.base import BinaryEnvelope
41 from vcsserver.lib.rc_json import json
41 from vcsserver.lib.rc_json import json
42 from vcsserver.config.settings_maker import SettingsMaker
42 from vcsserver.config.settings_maker import SettingsMaker
43 from vcsserver.str_utils import safe_int, safe_bytes, safe_str
43 from vcsserver.str_utils import safe_int, safe_bytes, safe_str
44 from vcsserver.lib.statsd_client import StatsdClient
44 from vcsserver.lib.statsd_client import StatsdClient
45
45
46 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
47
47
48 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
48 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
49 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
49 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
50
50
51 try:
51 try:
52 locale.setlocale(locale.LC_ALL, '')
52 locale.setlocale(locale.LC_ALL, '')
53 except locale.Error as e:
53 except locale.Error as e:
54 log.error(
54 log.error(
55 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
55 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
56 os.environ['LC_ALL'] = 'C'
56 os.environ['LC_ALL'] = 'C'
57
57
58
58
59 import vcsserver
59 import vcsserver
60 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
60 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
61 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
61 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
62 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
62 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
63 from vcsserver.echo_stub.echo_app import EchoApp
63 from vcsserver.echo_stub.echo_app import EchoApp
64 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
64 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
65 from vcsserver.lib.exc_tracking import store_exception
65 from vcsserver.lib.exc_tracking import store_exception
66 from vcsserver.server import VcsServer
66 from vcsserver.server import VcsServer
67
67
68 strict_vcs = True
68 strict_vcs = True
69
69
70 git_import_err = None
70 git_import_err = None
71 try:
71 try:
72 from vcsserver.remote.git import GitFactory, GitRemote
72 from vcsserver.remote.git import GitFactory, GitRemote
73 except ImportError as e:
73 except ImportError as e:
74 GitFactory = None
74 GitFactory = None
75 GitRemote = None
75 GitRemote = None
76 git_import_err = e
76 git_import_err = e
77 if strict_vcs:
77 if strict_vcs:
78 raise
78 raise
79
79
80
80
81 hg_import_err = None
81 hg_import_err = None
82 try:
82 try:
83 from vcsserver.remote.hg import MercurialFactory, HgRemote
83 from vcsserver.remote.hg import MercurialFactory, HgRemote
84 except ImportError as e:
84 except ImportError as e:
85 MercurialFactory = None
85 MercurialFactory = None
86 HgRemote = None
86 HgRemote = None
87 hg_import_err = e
87 hg_import_err = e
88 if strict_vcs:
88 if strict_vcs:
89 raise
89 raise
90
90
91
91
92 svn_import_err = None
92 svn_import_err = None
93 try:
93 try:
94 from vcsserver.remote.svn import SubversionFactory, SvnRemote
94 from vcsserver.remote.svn import SubversionFactory, SvnRemote
95 except ImportError as e:
95 except ImportError as e:
96 SubversionFactory = None
96 SubversionFactory = None
97 SvnRemote = None
97 SvnRemote = None
98 svn_import_err = e
98 svn_import_err = e
99 if strict_vcs:
99 if strict_vcs:
100 raise
100 raise
101
101
102
102
103 def _is_request_chunked(environ):
103 def _is_request_chunked(environ):
104 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
104 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
105 return stream
105 return stream
106
106
107
107
108 def log_max_fd():
108 def log_max_fd():
109 try:
109 try:
110 maxfd = psutil.Process().rlimit(psutil.RLIMIT_NOFILE)[1]
110 maxfd = psutil.Process().rlimit(psutil.RLIMIT_NOFILE)[1]
111 log.info('Max file descriptors value: %s', maxfd)
111 log.info('Max file descriptors value: %s', maxfd)
112 except Exception:
112 except Exception:
113 pass
113 pass
114
114
115
115
116 class VCS(object):
116 class VCS(object):
117 def __init__(self, locale_conf=None, cache_config=None):
117 def __init__(self, locale_conf=None, cache_config=None):
118 self.locale = locale_conf
118 self.locale = locale_conf
119 self.cache_config = cache_config
119 self.cache_config = cache_config
120 self._configure_locale()
120 self._configure_locale()
121
121
122 log_max_fd()
122 log_max_fd()
123
123
124 if GitFactory and GitRemote:
124 if GitFactory and GitRemote:
125 git_factory = GitFactory()
125 git_factory = GitFactory()
126 self._git_remote = GitRemote(git_factory)
126 self._git_remote = GitRemote(git_factory)
127 else:
127 else:
128 log.error("Git client import failed: %s", git_import_err)
128 log.error("Git client import failed: %s", git_import_err)
129
129
130 if MercurialFactory and HgRemote:
130 if MercurialFactory and HgRemote:
131 hg_factory = MercurialFactory()
131 hg_factory = MercurialFactory()
132 self._hg_remote = HgRemote(hg_factory)
132 self._hg_remote = HgRemote(hg_factory)
133 else:
133 else:
134 log.error("Mercurial client import failed: %s", hg_import_err)
134 log.error("Mercurial client import failed: %s", hg_import_err)
135
135
136 if SubversionFactory and SvnRemote:
136 if SubversionFactory and SvnRemote:
137 svn_factory = SubversionFactory()
137 svn_factory = SubversionFactory()
138
138
139 # hg factory is used for svn url validation
139 # hg factory is used for svn url validation
140 hg_factory = MercurialFactory()
140 hg_factory = MercurialFactory()
141 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
141 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
142 else:
142 else:
143 log.error("Subversion client import failed: %s", svn_import_err)
143 log.error("Subversion client import failed: %s", svn_import_err)
144
144
145 self._vcsserver = VcsServer()
145 self._vcsserver = VcsServer()
146
146
147 def _configure_locale(self):
147 def _configure_locale(self):
148 if self.locale:
148 if self.locale:
149 log.info('Settings locale: `LC_ALL` to %s', self.locale)
149 log.info('Settings locale: `LC_ALL` to %s', self.locale)
150 else:
150 else:
151 log.info('Configuring locale subsystem based on environment variables')
151 log.info('Configuring locale subsystem based on environment variables')
152 try:
152 try:
153 # If self.locale is the empty string, then the locale
153 # If self.locale is the empty string, then the locale
154 # module will use the environment variables. See the
154 # module will use the environment variables. See the
155 # documentation of the package `locale`.
155 # documentation of the package `locale`.
156 locale.setlocale(locale.LC_ALL, self.locale)
156 locale.setlocale(locale.LC_ALL, self.locale)
157
157
158 language_code, encoding = locale.getlocale()
158 language_code, encoding = locale.getlocale()
159 log.info(
159 log.info(
160 'Locale set to language code "%s" with encoding "%s".',
160 'Locale set to language code "%s" with encoding "%s".',
161 language_code, encoding)
161 language_code, encoding)
162 except locale.Error:
162 except locale.Error:
163 log.exception('Cannot set locale, not configuring the locale system')
163 log.exception('Cannot set locale, not configuring the locale system')
164
164
165
165
166 class WsgiProxy(object):
166 class WsgiProxy(object):
167 def __init__(self, wsgi):
167 def __init__(self, wsgi):
168 self.wsgi = wsgi
168 self.wsgi = wsgi
169
169
170 def __call__(self, environ, start_response):
170 def __call__(self, environ, start_response):
171 input_data = environ['wsgi.input'].read()
171 input_data = environ['wsgi.input'].read()
172 input_data = msgpack.unpackb(input_data)
172 input_data = msgpack.unpackb(input_data)
173
173
174 error = None
174 error = None
175 try:
175 try:
176 data, status, headers = self.wsgi.handle(
176 data, status, headers = self.wsgi.handle(
177 input_data['environment'], input_data['input_data'],
177 input_data['environment'], input_data['input_data'],
178 *input_data['args'], **input_data['kwargs'])
178 *input_data['args'], **input_data['kwargs'])
179 except Exception as e:
179 except Exception as e:
180 data, status, headers = [], None, None
180 data, status, headers = [], None, None
181 error = {
181 error = {
182 'message': str(e),
182 'message': str(e),
183 '_vcs_kind': getattr(e, '_vcs_kind', None)
183 '_vcs_kind': getattr(e, '_vcs_kind', None)
184 }
184 }
185
185
186 start_response(200, {})
186 start_response(200, {})
187 return self._iterator(error, status, headers, data)
187 return self._iterator(error, status, headers, data)
188
188
189 def _iterator(self, error, status, headers, data):
189 def _iterator(self, error, status, headers, data):
190 initial_data = [
190 initial_data = [
191 error,
191 error,
192 status,
192 status,
193 headers,
193 headers,
194 ]
194 ]
195
195
196 for d in chain(initial_data, data):
196 for d in chain(initial_data, data):
197 yield msgpack.packb(d)
197 yield msgpack.packb(d)
198
198
199
199
200 def not_found(request):
200 def not_found(request):
201 return {'status': '404 NOT FOUND'}
201 return {'status': '404 NOT FOUND'}
202
202
203
203
204 class VCSViewPredicate(object):
204 class VCSViewPredicate(object):
205 def __init__(self, val, config):
205 def __init__(self, val, config):
206 self.remotes = val
206 self.remotes = val
207
207
208 def text(self):
208 def text(self):
209 return 'vcs view method = %s' % (list(self.remotes.keys()),)
209 return 'vcs view method = %s' % (list(self.remotes.keys()),)
210
210
211 phash = text
211 phash = text
212
212
213 def __call__(self, context, request):
213 def __call__(self, context, request):
214 """
214 """
215 View predicate that returns true if given backend is supported by
215 View predicate that returns true if given backend is supported by
216 defined remotes.
216 defined remotes.
217 """
217 """
218 backend = request.matchdict.get('backend')
218 backend = request.matchdict.get('backend')
219 return backend in self.remotes
219 return backend in self.remotes
220
220
221
221
222 class HTTPApplication(object):
222 class HTTPApplication(object):
223 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
223 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
224
224
225 remote_wsgi = remote_wsgi
225 remote_wsgi = remote_wsgi
226 _use_echo_app = False
226 _use_echo_app = False
227
227
228 def __init__(self, settings=None, global_config=None):
228 def __init__(self, settings=None, global_config=None):
229
229
230 self.config = Configurator(settings=settings)
230 self.config = Configurator(settings=settings)
231 # Init our statsd at very start
231 # Init our statsd at very start
232 self.config.registry.statsd = StatsdClient.statsd
232 self.config.registry.statsd = StatsdClient.statsd
233 self.config.registry.vcs_call_context = {}
233 self.config.registry.vcs_call_context = {}
234
234
235 self.global_config = global_config
235 self.global_config = global_config
236 self.config.include('vcsserver.lib.rc_cache')
236 self.config.include('vcsserver.lib.rc_cache')
237
237
238 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
238 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
239 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
239 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
240 self._remotes = {
240 self._remotes = {
241 'hg': vcs._hg_remote,
241 'hg': vcs._hg_remote,
242 'git': vcs._git_remote,
242 'git': vcs._git_remote,
243 'svn': vcs._svn_remote,
243 'svn': vcs._svn_remote,
244 'server': vcs._vcsserver,
244 'server': vcs._vcsserver,
245 }
245 }
246 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
246 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
247 self._use_echo_app = True
247 self._use_echo_app = True
248 log.warning("Using EchoApp for VCS operations.")
248 log.warning("Using EchoApp for VCS operations.")
249 self.remote_wsgi = remote_wsgi_stub
249 self.remote_wsgi = remote_wsgi_stub
250
250
251 self._configure_settings(global_config, settings)
251 self._configure_settings(global_config, settings)
252
252
253 self._configure()
253 self._configure()
254
254
255 def _configure_settings(self, global_config, app_settings):
255 def _configure_settings(self, global_config, app_settings):
256 """
256 """
257 Configure the settings module.
257 Configure the settings module.
258 """
258 """
259 settings_merged = global_config.copy()
259 settings_merged = global_config.copy()
260 settings_merged.update(app_settings)
260 settings_merged.update(app_settings)
261
261
262 git_path = app_settings.get('git_path', None)
262 git_path = app_settings.get('git_path', None)
263 if git_path:
263 if git_path:
264 settings.GIT_EXECUTABLE = git_path
264 settings.GIT_EXECUTABLE = git_path
265 binary_dir = app_settings.get('core.binary_dir', None)
265 binary_dir = app_settings.get('core.binary_dir', None)
266 if binary_dir:
266 if binary_dir:
267 settings.BINARY_DIR = binary_dir
267 settings.BINARY_DIR = binary_dir
268
268
269 # Store the settings to make them available to other modules.
269 # Store the settings to make them available to other modules.
270 vcsserver.PYRAMID_SETTINGS = settings_merged
270 vcsserver.PYRAMID_SETTINGS = settings_merged
271 vcsserver.CONFIG = settings_merged
271 vcsserver.CONFIG = settings_merged
272
272
273 def _configure(self):
273 def _configure(self):
274 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
274 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
275
275
276 self.config.add_route('service', '/_service')
276 self.config.add_route('service', '/_service')
277 self.config.add_route('status', '/status')
277 self.config.add_route('status', '/status')
278 self.config.add_route('hg_proxy', '/proxy/hg')
278 self.config.add_route('hg_proxy', '/proxy/hg')
279 self.config.add_route('git_proxy', '/proxy/git')
279 self.config.add_route('git_proxy', '/proxy/git')
280
280
281 # rpc methods
281 # rpc methods
282 self.config.add_route('vcs', '/{backend}')
282 self.config.add_route('vcs', '/{backend}')
283
283
284 # streaming rpc remote methods
284 # streaming rpc remote methods
285 self.config.add_route('vcs_stream', '/{backend}/stream')
285 self.config.add_route('vcs_stream', '/{backend}/stream')
286
286
287 # vcs operations clone/push as streaming
287 # vcs operations clone/push as streaming
288 self.config.add_route('stream_git', '/stream/git/*repo_name')
288 self.config.add_route('stream_git', '/stream/git/*repo_name')
289 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
289 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
290
290
291 self.config.add_view(self.status_view, route_name='status', renderer='json')
291 self.config.add_view(self.status_view, route_name='status', renderer='json')
292 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
292 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
293
293
294 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
294 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
295 self.config.add_view(self.git_proxy(), route_name='git_proxy')
295 self.config.add_view(self.git_proxy(), route_name='git_proxy')
296 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
296 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
297 vcs_view=self._remotes)
297 vcs_view=self._remotes)
298 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
298 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
299 vcs_view=self._remotes)
299 vcs_view=self._remotes)
300
300
301 self.config.add_view(self.hg_stream(), route_name='stream_hg')
301 self.config.add_view(self.hg_stream(), route_name='stream_hg')
302 self.config.add_view(self.git_stream(), route_name='stream_git')
302 self.config.add_view(self.git_stream(), route_name='stream_git')
303
303
304 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
304 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
305
305
306 self.config.add_notfound_view(not_found, renderer='json')
306 self.config.add_notfound_view(not_found, renderer='json')
307
307
308 self.config.add_view(self.handle_vcs_exception, context=Exception)
308 self.config.add_view(self.handle_vcs_exception, context=Exception)
309
309
310 self.config.add_tween(
310 self.config.add_tween(
311 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
311 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
312 )
312 )
313 self.config.add_request_method(
313 self.config.add_request_method(
314 'vcsserver.lib.request_counter.get_request_counter',
314 'vcsserver.lib.request_counter.get_request_counter',
315 'request_count')
315 'request_count')
316
316
317 def wsgi_app(self):
317 def wsgi_app(self):
318 return self.config.make_wsgi_app()
318 return self.config.make_wsgi_app()
319
319
320 def _vcs_view_params(self, request):
320 def _vcs_view_params(self, request):
321 remote = self._remotes[request.matchdict['backend']]
321 remote = self._remotes[request.matchdict['backend']]
322 payload = msgpack.unpackb(request.body, use_list=True)
322 payload = msgpack.unpackb(request.body, use_list=True)
323
323
324 method = payload.get('method')
324 method = payload.get('method')
325 params = payload['params']
325 params = payload['params']
326 wire = params.get('wire')
326 wire = params.get('wire')
327 args = params.get('args')
327 args = params.get('args')
328 kwargs = params.get('kwargs')
328 kwargs = params.get('kwargs')
329 context_uid = None
329 context_uid = None
330
330
331 request.registry.vcs_call_context = {
331 request.registry.vcs_call_context = {
332 'method': method,
332 'method': method,
333 'repo_name': payload.get('_repo_name')
333 'repo_name': payload.get('_repo_name')
334 }
334 }
335
335
336 if wire:
336 if wire:
337 try:
337 try:
338 wire['context'] = context_uid = uuid.UUID(wire['context'])
338 wire['context'] = context_uid = uuid.UUID(wire['context'])
339 except KeyError:
339 except KeyError:
340 pass
340 pass
341 args.insert(0, wire)
341 args.insert(0, wire)
342 repo_state_uid = wire.get('repo_state_uid') if wire else None
342 repo_state_uid = wire.get('repo_state_uid') if wire else None
343
343
344 # NOTE(marcink): trading complexity for slight performance
344 # NOTE(marcink): trading complexity for slight performance
345 if log.isEnabledFor(logging.DEBUG):
345 if log.isEnabledFor(logging.DEBUG):
346 # also we SKIP printing out any of those methods args since they maybe excessive
346 # also we SKIP printing out any of those methods args since they maybe excessive
347 just_args_methods = {
347 just_args_methods = {
348 'commitctx': ('content', 'removed', 'updated')
348 'commitctx': ('content', 'removed', 'updated')
349 }
349 }
350 if method in just_args_methods:
350 if method in just_args_methods:
351 skip_args = just_args_methods[method]
351 skip_args = just_args_methods[method]
352 call_args = ''
352 call_args = ''
353 call_kwargs = {}
353 call_kwargs = {}
354 for k in kwargs:
354 for k in kwargs:
355 if k in skip_args:
355 if k in skip_args:
356 # replace our skip key with dummy
356 # replace our skip key with dummy
357 call_kwargs[k] = f'RemovedParam({k})'
357 call_kwargs[k] = f'RemovedParam({k})'
358 else:
358 else:
359 call_kwargs[k] = kwargs[k]
359 call_kwargs[k] = kwargs[k]
360 else:
360 else:
361 call_args = args[1:]
361 call_args = args[1:]
362 call_kwargs = kwargs
362 call_kwargs = kwargs
363
363
364 log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
364 log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
365 method, call_args, call_kwargs, context_uid, repo_state_uid)
365 method, call_args, call_kwargs, context_uid, repo_state_uid)
366
366
367 statsd = request.registry.statsd
367 statsd = request.registry.statsd
368 if statsd:
368 if statsd:
369 statsd.incr(
369 statsd.incr(
370 'vcsserver_method_total', tags=[
370 'vcsserver_method_total', tags=[
371 "method:{}".format(method),
371 "method:{}".format(method),
372 ])
372 ])
373 return payload, remote, method, args, kwargs
373 return payload, remote, method, args, kwargs
374
374
375 def vcs_view(self, request):
375 def vcs_view(self, request):
376
376
377 payload, remote, method, args, kwargs = self._vcs_view_params(request)
377 payload, remote, method, args, kwargs = self._vcs_view_params(request)
378 payload_id = payload.get('id')
378 payload_id = payload.get('id')
379
379
380 try:
380 try:
381 resp = getattr(remote, method)(*args, **kwargs)
381 resp = getattr(remote, method)(*args, **kwargs)
382 except Exception as e:
382 except Exception as e:
383 exc_info = list(sys.exc_info())
383 exc_info = list(sys.exc_info())
384 exc_type, exc_value, exc_traceback = exc_info
384 exc_type, exc_value, exc_traceback = exc_info
385
385
386 org_exc = getattr(e, '_org_exc', None)
386 org_exc = getattr(e, '_org_exc', None)
387 org_exc_name = None
387 org_exc_name = None
388 org_exc_tb = ''
388 org_exc_tb = ''
389 if org_exc:
389 if org_exc:
390 org_exc_name = org_exc.__class__.__name__
390 org_exc_name = org_exc.__class__.__name__
391 org_exc_tb = getattr(e, '_org_exc_tb', '')
391 org_exc_tb = getattr(e, '_org_exc_tb', '')
392 # replace our "faked" exception with our org
392 # replace our "faked" exception with our org
393 exc_info[0] = org_exc.__class__
393 exc_info[0] = org_exc.__class__
394 exc_info[1] = org_exc
394 exc_info[1] = org_exc
395
395
396 should_store_exc = True
396 should_store_exc = True
397 if org_exc:
397 if org_exc:
398 def get_exc_fqn(_exc_obj):
398 def get_exc_fqn(_exc_obj):
399 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
399 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
400 return module_name + '.' + org_exc_name
400 return module_name + '.' + org_exc_name
401
401
402 exc_fqn = get_exc_fqn(org_exc)
402 exc_fqn = get_exc_fqn(org_exc)
403
403
404 if exc_fqn in ['mercurial.error.RepoLookupError',
404 if exc_fqn in ['mercurial.error.RepoLookupError',
405 'vcsserver.exceptions.RefNotFoundException']:
405 'vcsserver.exceptions.RefNotFoundException']:
406 should_store_exc = False
406 should_store_exc = False
407
407
408 if should_store_exc:
408 if should_store_exc:
409 store_exception(id(exc_info), exc_info, request_path=request.path)
409 store_exception(id(exc_info), exc_info, request_path=request.path)
410
410
411 tb_info = ''.join(
411 tb_info = ''.join(
412 traceback.format_exception(exc_type, exc_value, exc_traceback))
412 traceback.format_exception(exc_type, exc_value, exc_traceback))
413
413
414 type_ = e.__class__.__name__
414 type_ = e.__class__.__name__
415 if type_ not in self.ALLOWED_EXCEPTIONS:
415 if type_ not in self.ALLOWED_EXCEPTIONS:
416 type_ = None
416 type_ = None
417
417
418 resp = {
418 resp = {
419 'id': payload_id,
419 'id': payload_id,
420 'error': {
420 'error': {
421 'message': str(e),
421 'message': str(e),
422 'traceback': tb_info,
422 'traceback': tb_info,
423 'org_exc': org_exc_name,
423 'org_exc': org_exc_name,
424 'org_exc_tb': org_exc_tb,
424 'org_exc_tb': org_exc_tb,
425 'type': type_
425 'type': type_
426 }
426 }
427 }
427 }
428
428
429 try:
429 try:
430 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
430 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
431 except AttributeError:
431 except AttributeError:
432 pass
432 pass
433 else:
433 else:
434 resp = {
434 resp = {
435 'id': payload_id,
435 'id': payload_id,
436 'result': resp
436 'result': resp
437 }
437 }
438 log.debug('Serving data for method %s', method)
438 log.debug('Serving data for method %s', method)
439 return resp
439 return resp
440
440
441 def vcs_stream_view(self, request):
441 def vcs_stream_view(self, request):
442 payload, remote, method, args, kwargs = self._vcs_view_params(request)
442 payload, remote, method, args, kwargs = self._vcs_view_params(request)
443 # this method has a stream: marker we remove it here
443 # this method has a stream: marker we remove it here
444 method = method.split('stream:')[-1]
444 method = method.split('stream:')[-1]
445 chunk_size = safe_int(payload.get('chunk_size')) or 4096
445 chunk_size = safe_int(payload.get('chunk_size')) or 4096
446
446
447 try:
447 try:
448 resp = getattr(remote, method)(*args, **kwargs)
448 resp = getattr(remote, method)(*args, **kwargs)
449 except Exception as e:
449 except Exception as e:
450 raise
450 raise
451
451
452 def get_chunked_data(method_resp):
452 def get_chunked_data(method_resp):
453 stream = io.BytesIO(method_resp)
453 stream = io.BytesIO(method_resp)
454 while 1:
454 while 1:
455 chunk = stream.read(chunk_size)
455 chunk = stream.read(chunk_size)
456 if not chunk:
456 if not chunk:
457 break
457 break
458 yield chunk
458 yield chunk
459
459
460 response = Response(app_iter=get_chunked_data(resp))
460 response = Response(app_iter=get_chunked_data(resp))
461 response.content_type = 'application/octet-stream'
461 response.content_type = 'application/octet-stream'
462
462
463 return response
463 return response
464
464
465 def status_view(self, request):
465 def status_view(self, request):
466 import vcsserver
466 import vcsserver
467 return {'status': 'OK', 'vcsserver_version': safe_str(vcsserver.__version__),
467 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
468 'pid': os.getpid()}
468 'pid': os.getpid()}
469
469
470 def service_view(self, request):
470 def service_view(self, request):
471 import vcsserver
471 import vcsserver
472
472
473 payload = msgpack.unpackb(request.body, use_list=True)
473 payload = msgpack.unpackb(request.body, use_list=True)
474 server_config, app_config = {}, {}
474 server_config, app_config = {}, {}
475
475
476 try:
476 try:
477 path = self.global_config['__file__']
477 path = self.global_config['__file__']
478 config = configparser.RawConfigParser()
478 config = configparser.RawConfigParser()
479
479
480 config.read(path)
480 config.read(path)
481
481
482 if config.has_section('server:main'):
482 if config.has_section('server:main'):
483 server_config = dict(config.items('server:main'))
483 server_config = dict(config.items('server:main'))
484 if config.has_section('app:main'):
484 if config.has_section('app:main'):
485 app_config = dict(config.items('app:main'))
485 app_config = dict(config.items('app:main'))
486
486
487 except Exception:
487 except Exception:
488 log.exception('Failed to read .ini file for display')
488 log.exception('Failed to read .ini file for display')
489
489
490 environ = list(os.environ.items())
490 environ = list(os.environ.items())
491
491
492 resp = {
492 resp = {
493 'id': payload.get('id'),
493 'id': payload.get('id'),
494 'result': dict(
494 'result': dict(
495 version=safe_str(vcsserver.__version__),
495 version=vcsserver.__version__,
496 config=server_config,
496 config=server_config,
497 app_config=app_config,
497 app_config=app_config,
498 environ=environ,
498 environ=environ,
499 payload=payload,
499 payload=payload,
500 )
500 )
501 }
501 }
502 return resp
502 return resp
503
503
504 def _msgpack_renderer_factory(self, info):
504 def _msgpack_renderer_factory(self, info):
505
505
506 def _render(value, system):
506 def _render(value, system):
507 bin_type = False
507 bin_type = False
508 res = value.get('result')
508 res = value.get('result')
509 if res and isinstance(res, BinaryEnvelope):
509 if res and isinstance(res, BinaryEnvelope):
510 value['result'] = res.value
510 value['result'] = res.value
511 bin_type = res.bin_type
511 bin_type = res.bin_type
512
512
513 request = system.get('request')
513 request = system.get('request')
514 if request is not None:
514 if request is not None:
515 response = request.response
515 response = request.response
516 ct = response.content_type
516 ct = response.content_type
517 if ct == response.default_content_type:
517 if ct == response.default_content_type:
518 response.content_type = 'application/x-msgpack'
518 response.content_type = 'application/x-msgpack'
519
519
520 return msgpack.packb(value, use_bin_type=bin_type)
520 return msgpack.packb(value, use_bin_type=bin_type)
521 return _render
521 return _render
522
522
523 def set_env_from_config(self, environ, config):
523 def set_env_from_config(self, environ, config):
524 dict_conf = {}
524 dict_conf = {}
525 try:
525 try:
526 for elem in config:
526 for elem in config:
527 if elem[0] == 'rhodecode':
527 if elem[0] == 'rhodecode':
528 dict_conf = json.loads(elem[2])
528 dict_conf = json.loads(elem[2])
529 break
529 break
530 except Exception:
530 except Exception:
531 log.exception('Failed to fetch SCM CONFIG')
531 log.exception('Failed to fetch SCM CONFIG')
532 return
532 return
533
533
534 username = dict_conf.get('username')
534 username = dict_conf.get('username')
535 if username:
535 if username:
536 environ['REMOTE_USER'] = username
536 environ['REMOTE_USER'] = username
537 # mercurial specific, some extension api rely on this
537 # mercurial specific, some extension api rely on this
538 environ['HGUSER'] = username
538 environ['HGUSER'] = username
539
539
540 ip = dict_conf.get('ip')
540 ip = dict_conf.get('ip')
541 if ip:
541 if ip:
542 environ['REMOTE_HOST'] = ip
542 environ['REMOTE_HOST'] = ip
543
543
544 if _is_request_chunked(environ):
544 if _is_request_chunked(environ):
545 # set the compatibility flag for webob
545 # set the compatibility flag for webob
546 environ['wsgi.input_terminated'] = True
546 environ['wsgi.input_terminated'] = True
547
547
548 def hg_proxy(self):
548 def hg_proxy(self):
549 @wsgiapp
549 @wsgiapp
550 def _hg_proxy(environ, start_response):
550 def _hg_proxy(environ, start_response):
551 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
551 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
552 return app(environ, start_response)
552 return app(environ, start_response)
553 return _hg_proxy
553 return _hg_proxy
554
554
555 def git_proxy(self):
555 def git_proxy(self):
556 @wsgiapp
556 @wsgiapp
557 def _git_proxy(environ, start_response):
557 def _git_proxy(environ, start_response):
558 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
558 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
559 return app(environ, start_response)
559 return app(environ, start_response)
560 return _git_proxy
560 return _git_proxy
561
561
562 def hg_stream(self):
562 def hg_stream(self):
563 if self._use_echo_app:
563 if self._use_echo_app:
564 @wsgiapp
564 @wsgiapp
565 def _hg_stream(environ, start_response):
565 def _hg_stream(environ, start_response):
566 app = EchoApp('fake_path', 'fake_name', None)
566 app = EchoApp('fake_path', 'fake_name', None)
567 return app(environ, start_response)
567 return app(environ, start_response)
568 return _hg_stream
568 return _hg_stream
569 else:
569 else:
570 @wsgiapp
570 @wsgiapp
571 def _hg_stream(environ, start_response):
571 def _hg_stream(environ, start_response):
572 log.debug('http-app: handling hg stream')
572 log.debug('http-app: handling hg stream')
573
573
574 packed_cc = base64.b64decode(environ['HTTP_X_RC_VCS_STREAM_CALL_CONTEXT'])
574 packed_cc = base64.b64decode(environ['HTTP_X_RC_VCS_STREAM_CALL_CONTEXT'])
575 call_context = msgpack.unpackb(packed_cc)
575 call_context = msgpack.unpackb(packed_cc)
576
576
577 repo_path = call_context['repo_path']
577 repo_path = call_context['repo_path']
578 repo_name = call_context['repo_name']
578 repo_name = call_context['repo_name']
579 config = call_context['repo_config']
579 config = call_context['repo_config']
580
580
581 app = scm_app.create_hg_wsgi_app(
581 app = scm_app.create_hg_wsgi_app(
582 repo_path, repo_name, config)
582 repo_path, repo_name, config)
583
583
584 # Consistent path information for hgweb
584 # Consistent path information for hgweb
585 environ['PATH_INFO'] = call_context['path_info']
585 environ['PATH_INFO'] = call_context['path_info']
586 environ['REPO_NAME'] = repo_name
586 environ['REPO_NAME'] = repo_name
587 self.set_env_from_config(environ, config)
587 self.set_env_from_config(environ, config)
588
588
589 log.debug('http-app: starting app handler '
589 log.debug('http-app: starting app handler '
590 'with %s and process request', app)
590 'with %s and process request', app)
591 return app(environ, ResponseFilter(start_response))
591 return app(environ, ResponseFilter(start_response))
592 return _hg_stream
592 return _hg_stream
593
593
594 def git_stream(self):
594 def git_stream(self):
595 if self._use_echo_app:
595 if self._use_echo_app:
596 @wsgiapp
596 @wsgiapp
597 def _git_stream(environ, start_response):
597 def _git_stream(environ, start_response):
598 app = EchoApp('fake_path', 'fake_name', None)
598 app = EchoApp('fake_path', 'fake_name', None)
599 return app(environ, start_response)
599 return app(environ, start_response)
600 return _git_stream
600 return _git_stream
601 else:
601 else:
602 @wsgiapp
602 @wsgiapp
603 def _git_stream(environ, start_response):
603 def _git_stream(environ, start_response):
604 log.debug('http-app: handling git stream')
604 log.debug('http-app: handling git stream')
605
605
606 packed_cc = base64.b64decode(environ['HTTP_X_RC_VCS_STREAM_CALL_CONTEXT'])
606 packed_cc = base64.b64decode(environ['HTTP_X_RC_VCS_STREAM_CALL_CONTEXT'])
607 call_context = msgpack.unpackb(packed_cc)
607 call_context = msgpack.unpackb(packed_cc)
608
608
609 repo_path = call_context['repo_path']
609 repo_path = call_context['repo_path']
610 repo_name = call_context['repo_name']
610 repo_name = call_context['repo_name']
611 config = call_context['repo_config']
611 config = call_context['repo_config']
612
612
613 environ['PATH_INFO'] = call_context['path_info']
613 environ['PATH_INFO'] = call_context['path_info']
614 self.set_env_from_config(environ, config)
614 self.set_env_from_config(environ, config)
615
615
616 content_type = environ.get('CONTENT_TYPE', '')
616 content_type = environ.get('CONTENT_TYPE', '')
617
617
618 path = environ['PATH_INFO']
618 path = environ['PATH_INFO']
619 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
619 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
620 log.debug(
620 log.debug(
621 'LFS: Detecting if request `%s` is LFS server path based '
621 'LFS: Detecting if request `%s` is LFS server path based '
622 'on content type:`%s`, is_lfs:%s',
622 'on content type:`%s`, is_lfs:%s',
623 path, content_type, is_lfs_request)
623 path, content_type, is_lfs_request)
624
624
625 if not is_lfs_request:
625 if not is_lfs_request:
626 # fallback detection by path
626 # fallback detection by path
627 if GIT_LFS_PROTO_PAT.match(path):
627 if GIT_LFS_PROTO_PAT.match(path):
628 is_lfs_request = True
628 is_lfs_request = True
629 log.debug(
629 log.debug(
630 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
630 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
631 path, is_lfs_request)
631 path, is_lfs_request)
632
632
633 if is_lfs_request:
633 if is_lfs_request:
634 app = scm_app.create_git_lfs_wsgi_app(
634 app = scm_app.create_git_lfs_wsgi_app(
635 repo_path, repo_name, config)
635 repo_path, repo_name, config)
636 else:
636 else:
637 app = scm_app.create_git_wsgi_app(
637 app = scm_app.create_git_wsgi_app(
638 repo_path, repo_name, config)
638 repo_path, repo_name, config)
639
639
640 log.debug('http-app: starting app handler '
640 log.debug('http-app: starting app handler '
641 'with %s and process request', app)
641 'with %s and process request', app)
642
642
643 return app(environ, start_response)
643 return app(environ, start_response)
644
644
645 return _git_stream
645 return _git_stream
646
646
647 def handle_vcs_exception(self, exception, request):
647 def handle_vcs_exception(self, exception, request):
648 _vcs_kind = getattr(exception, '_vcs_kind', '')
648 _vcs_kind = getattr(exception, '_vcs_kind', '')
649 if _vcs_kind == 'repo_locked':
649 if _vcs_kind == 'repo_locked':
650 # Get custom repo-locked status code if present.
650 # Get custom repo-locked status code if present.
651 status_code = request.headers.get('X-RC-Locked-Status-Code')
651 status_code = request.headers.get('X-RC-Locked-Status-Code')
652 return HTTPRepoLocked(
652 return HTTPRepoLocked(
653 title=exception.message, status_code=status_code)
653 title=exception.message, status_code=status_code)
654
654
655 elif _vcs_kind == 'repo_branch_protected':
655 elif _vcs_kind == 'repo_branch_protected':
656 # Get custom repo-branch-protected status code if present.
656 # Get custom repo-branch-protected status code if present.
657 return HTTPRepoBranchProtected(title=exception.message)
657 return HTTPRepoBranchProtected(title=exception.message)
658
658
659 exc_info = request.exc_info
659 exc_info = request.exc_info
660 store_exception(id(exc_info), exc_info)
660 store_exception(id(exc_info), exc_info)
661
661
662 traceback_info = 'unavailable'
662 traceback_info = 'unavailable'
663 if request.exc_info:
663 if request.exc_info:
664 exc_type, exc_value, exc_tb = request.exc_info
664 exc_type, exc_value, exc_tb = request.exc_info
665 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
665 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
666
666
667 log.error(
667 log.error(
668 'error occurred handling this request for path: %s, \n tb: %s',
668 'error occurred handling this request for path: %s, \n tb: %s',
669 request.path, traceback_info)
669 request.path, traceback_info)
670
670
671 statsd = request.registry.statsd
671 statsd = request.registry.statsd
672 if statsd:
672 if statsd:
673 exc_type = "{}.{}".format(exception.__class__.__module__, exception.__class__.__name__)
673 exc_type = "{}.{}".format(exception.__class__.__module__, exception.__class__.__name__)
674 statsd.incr('vcsserver_exception_total',
674 statsd.incr('vcsserver_exception_total',
675 tags=["type:{}".format(exc_type)])
675 tags=["type:{}".format(exc_type)])
676 raise exception
676 raise exception
677
677
678
678
679 class ResponseFilter(object):
679 class ResponseFilter(object):
680
680
681 def __init__(self, start_response):
681 def __init__(self, start_response):
682 self._start_response = start_response
682 self._start_response = start_response
683
683
684 def __call__(self, status, response_headers, exc_info=None):
684 def __call__(self, status, response_headers, exc_info=None):
685 headers = tuple(
685 headers = tuple(
686 (h, v) for h, v in response_headers
686 (h, v) for h, v in response_headers
687 if not wsgiref.util.is_hop_by_hop(h))
687 if not wsgiref.util.is_hop_by_hop(h))
688 return self._start_response(status, headers, exc_info)
688 return self._start_response(status, headers, exc_info)
689
689
690
690
691 def sanitize_settings_and_apply_defaults(global_config, settings):
691 def sanitize_settings_and_apply_defaults(global_config, settings):
692 global_settings_maker = SettingsMaker(global_config)
692 global_settings_maker = SettingsMaker(global_config)
693 settings_maker = SettingsMaker(settings)
693 settings_maker = SettingsMaker(settings)
694
694
695 settings_maker.make_setting('logging.autoconfigure', False, parser='bool')
695 settings_maker.make_setting('logging.autoconfigure', False, parser='bool')
696
696
697 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
697 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
698 settings_maker.enable_logging(logging_conf)
698 settings_maker.enable_logging(logging_conf)
699
699
700 # Default includes, possible to change as a user
700 # Default includes, possible to change as a user
701 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
701 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
702 log.debug("Using the following pyramid.includes: %s", pyramid_includes)
702 log.debug("Using the following pyramid.includes: %s", pyramid_includes)
703
703
704 settings_maker.make_setting('__file__', global_config.get('__file__'))
704 settings_maker.make_setting('__file__', global_config.get('__file__'))
705
705
706 settings_maker.make_setting('pyramid.default_locale_name', 'en')
706 settings_maker.make_setting('pyramid.default_locale_name', 'en')
707 settings_maker.make_setting('locale', 'en_US.UTF-8')
707 settings_maker.make_setting('locale', 'en_US.UTF-8')
708
708
709 settings_maker.make_setting('core.binary_dir', '')
709 settings_maker.make_setting('core.binary_dir', '')
710
710
711 temp_store = tempfile.gettempdir()
711 temp_store = tempfile.gettempdir()
712 default_cache_dir = os.path.join(temp_store, 'rc_cache')
712 default_cache_dir = os.path.join(temp_store, 'rc_cache')
713 # save default, cache dir, and use it for all backends later.
713 # save default, cache dir, and use it for all backends later.
714 default_cache_dir = settings_maker.make_setting(
714 default_cache_dir = settings_maker.make_setting(
715 'cache_dir',
715 'cache_dir',
716 default=default_cache_dir, default_when_empty=True,
716 default=default_cache_dir, default_when_empty=True,
717 parser='dir:ensured')
717 parser='dir:ensured')
718
718
719 # exception store cache
719 # exception store cache
720 settings_maker.make_setting(
720 settings_maker.make_setting(
721 'exception_tracker.store_path',
721 'exception_tracker.store_path',
722 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
722 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
723 parser='dir:ensured'
723 parser='dir:ensured'
724 )
724 )
725
725
726 # repo_object cache defaults
726 # repo_object cache defaults
727 settings_maker.make_setting(
727 settings_maker.make_setting(
728 'rc_cache.repo_object.backend',
728 'rc_cache.repo_object.backend',
729 default='dogpile.cache.rc.file_namespace',
729 default='dogpile.cache.rc.file_namespace',
730 parser='string')
730 parser='string')
731 settings_maker.make_setting(
731 settings_maker.make_setting(
732 'rc_cache.repo_object.expiration_time',
732 'rc_cache.repo_object.expiration_time',
733 default=30 * 24 * 60 * 60, # 30days
733 default=30 * 24 * 60 * 60, # 30days
734 parser='int')
734 parser='int')
735 settings_maker.make_setting(
735 settings_maker.make_setting(
736 'rc_cache.repo_object.arguments.filename',
736 'rc_cache.repo_object.arguments.filename',
737 default=os.path.join(default_cache_dir, 'vcsserver_cache_repo_object.db'),
737 default=os.path.join(default_cache_dir, 'vcsserver_cache_repo_object.db'),
738 parser='string')
738 parser='string')
739
739
740 # statsd
740 # statsd
741 settings_maker.make_setting('statsd.enabled', False, parser='bool')
741 settings_maker.make_setting('statsd.enabled', False, parser='bool')
742 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
742 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
743 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
743 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
744 settings_maker.make_setting('statsd.statsd_prefix', '')
744 settings_maker.make_setting('statsd.statsd_prefix', '')
745 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
745 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
746
746
747 settings_maker.env_expand()
747 settings_maker.env_expand()
748
748
749
749
750 def main(global_config, **settings):
750 def main(global_config, **settings):
751 start_time = time.time()
751 start_time = time.time()
752 log.info('Pyramid app config starting')
752 log.info('Pyramid app config starting')
753
753
754 if MercurialFactory:
754 if MercurialFactory:
755 hgpatches.patch_largefiles_capabilities()
755 hgpatches.patch_largefiles_capabilities()
756 hgpatches.patch_subrepo_type_mapping()
756 hgpatches.patch_subrepo_type_mapping()
757
757
758 # Fill in and sanitize the defaults & do ENV expansion
758 # Fill in and sanitize the defaults & do ENV expansion
759 sanitize_settings_and_apply_defaults(global_config, settings)
759 sanitize_settings_and_apply_defaults(global_config, settings)
760
760
761 # init and bootstrap StatsdClient
761 # init and bootstrap StatsdClient
762 StatsdClient.setup(settings)
762 StatsdClient.setup(settings)
763
763
764 pyramid_app = HTTPApplication(settings=settings, global_config=global_config).wsgi_app()
764 pyramid_app = HTTPApplication(settings=settings, global_config=global_config).wsgi_app()
765 total_time = time.time() - start_time
765 total_time = time.time() - start_time
766 log.info('Pyramid app `%s` created and configured in %.2fs',
766 log.info('Pyramid app `%s` created and configured in %.2fs',
767 getattr(pyramid_app, 'func_name', 'pyramid_app'), total_time)
767 getattr(pyramid_app, 'func_name', 'pyramid_app'), total_time)
768 return pyramid_app
768 return pyramid_app
769
769
770
770
@@ -1,206 +1,206 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2020 RhodeCode GmbH
2 # Copyright (C) 2014-2020 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
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 General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import os
18 import os
19 import sys
19 import sys
20 import stat
20 import stat
21 import pytest
21 import pytest
22 import vcsserver
22 import vcsserver
23 import tempfile
23 import tempfile
24 from vcsserver import hook_utils
24 from vcsserver import hook_utils
25 from vcsserver.tests.fixture import no_newline_id_generator
25 from vcsserver.tests.fixture import no_newline_id_generator
26 from vcsserver.str_utils import safe_bytes, safe_str
26 from vcsserver.str_utils import safe_bytes, safe_str
27 from vcsserver.utils import AttributeDict
27 from vcsserver.utils import AttributeDict
28
28
29
29
30 class TestCheckRhodecodeHook(object):
30 class TestCheckRhodecodeHook(object):
31
31
32 def test_returns_false_when_hook_file_is_wrong_found(self, tmpdir):
32 def test_returns_false_when_hook_file_is_wrong_found(self, tmpdir):
33 hook = os.path.join(str(tmpdir), 'fake_hook_file.py')
33 hook = os.path.join(str(tmpdir), 'fake_hook_file.py')
34 with open(hook, 'wb') as f:
34 with open(hook, 'wb') as f:
35 f.write(b'dummy test')
35 f.write(b'dummy test')
36 result = hook_utils.check_rhodecode_hook(hook)
36 result = hook_utils.check_rhodecode_hook(hook)
37 assert result is False
37 assert result is False
38
38
39 def test_returns_true_when_no_hook_file_found(self, tmpdir):
39 def test_returns_true_when_no_hook_file_found(self, tmpdir):
40 hook = os.path.join(str(tmpdir), 'fake_hook_file_not_existing.py')
40 hook = os.path.join(str(tmpdir), 'fake_hook_file_not_existing.py')
41 result = hook_utils.check_rhodecode_hook(hook)
41 result = hook_utils.check_rhodecode_hook(hook)
42 assert result
42 assert result
43
43
44 @pytest.mark.parametrize("file_content, expected_result", [
44 @pytest.mark.parametrize("file_content, expected_result", [
45 ("RC_HOOK_VER = '3.3.3'\n", True),
45 ("RC_HOOK_VER = '3.3.3'\n", True),
46 ("RC_HOOK = '3.3.3'\n", False),
46 ("RC_HOOK = '3.3.3'\n", False),
47 ], ids=no_newline_id_generator)
47 ], ids=no_newline_id_generator)
48 def test_signatures(self, file_content, expected_result, tmpdir):
48 def test_signatures(self, file_content, expected_result, tmpdir):
49 hook = os.path.join(str(tmpdir), 'fake_hook_file_1.py')
49 hook = os.path.join(str(tmpdir), 'fake_hook_file_1.py')
50 with open(hook, 'wb') as f:
50 with open(hook, 'wb') as f:
51 f.write(safe_bytes(file_content))
51 f.write(safe_bytes(file_content))
52
52
53 result = hook_utils.check_rhodecode_hook(hook)
53 result = hook_utils.check_rhodecode_hook(hook)
54
54
55 assert result is expected_result
55 assert result is expected_result
56
56
57
57
58 class BaseInstallHooks(object):
58 class BaseInstallHooks(object):
59 HOOK_FILES = ()
59 HOOK_FILES = ()
60
60
61 def _check_hook_file_mode(self, file_path):
61 def _check_hook_file_mode(self, file_path):
62 assert os.path.exists(file_path), 'path %s missing' % file_path
62 assert os.path.exists(file_path), 'path %s missing' % file_path
63 stat_info = os.stat(file_path)
63 stat_info = os.stat(file_path)
64
64
65 file_mode = stat.S_IMODE(stat_info.st_mode)
65 file_mode = stat.S_IMODE(stat_info.st_mode)
66 expected_mode = int('755', 8)
66 expected_mode = int('755', 8)
67 assert expected_mode == file_mode
67 assert expected_mode == file_mode
68
68
69 def _check_hook_file_content(self, file_path, executable):
69 def _check_hook_file_content(self, file_path, executable):
70 executable = executable or sys.executable
70 executable = executable or sys.executable
71 with open(file_path, 'rt') as hook_file:
71 with open(file_path, 'rt') as hook_file:
72 content = hook_file.read()
72 content = hook_file.read()
73
73
74 expected_env = '#!{}'.format(executable)
74 expected_env = '#!{}'.format(executable)
75 expected_rc_version = "\nRC_HOOK_VER = '{}'\n".format(safe_str(vcsserver.__version__))
75 expected_rc_version = "\nRC_HOOK_VER = '{}'\n".format(vcsserver.__version__)
76 assert content.strip().startswith(expected_env)
76 assert content.strip().startswith(expected_env)
77 assert expected_rc_version in content
77 assert expected_rc_version in content
78
78
79 def _create_fake_hook(self, file_path, content):
79 def _create_fake_hook(self, file_path, content):
80 with open(file_path, 'w') as hook_file:
80 with open(file_path, 'w') as hook_file:
81 hook_file.write(content)
81 hook_file.write(content)
82
82
83 def create_dummy_repo(self, repo_type):
83 def create_dummy_repo(self, repo_type):
84 tmpdir = tempfile.mkdtemp()
84 tmpdir = tempfile.mkdtemp()
85 repo = AttributeDict()
85 repo = AttributeDict()
86 if repo_type == 'git':
86 if repo_type == 'git':
87 repo.path = os.path.join(tmpdir, 'test_git_hooks_installation_repo')
87 repo.path = os.path.join(tmpdir, 'test_git_hooks_installation_repo')
88 os.makedirs(repo.path)
88 os.makedirs(repo.path)
89 os.makedirs(os.path.join(repo.path, 'hooks'))
89 os.makedirs(os.path.join(repo.path, 'hooks'))
90 repo.bare = True
90 repo.bare = True
91
91
92 elif repo_type == 'svn':
92 elif repo_type == 'svn':
93 repo.path = os.path.join(tmpdir, 'test_svn_hooks_installation_repo')
93 repo.path = os.path.join(tmpdir, 'test_svn_hooks_installation_repo')
94 os.makedirs(repo.path)
94 os.makedirs(repo.path)
95 os.makedirs(os.path.join(repo.path, 'hooks'))
95 os.makedirs(os.path.join(repo.path, 'hooks'))
96
96
97 return repo
97 return repo
98
98
99 def check_hooks(self, repo_path, repo_bare=True):
99 def check_hooks(self, repo_path, repo_bare=True):
100 for file_name in self.HOOK_FILES:
100 for file_name in self.HOOK_FILES:
101 if repo_bare:
101 if repo_bare:
102 file_path = os.path.join(repo_path, 'hooks', file_name)
102 file_path = os.path.join(repo_path, 'hooks', file_name)
103 else:
103 else:
104 file_path = os.path.join(repo_path, '.git', 'hooks', file_name)
104 file_path = os.path.join(repo_path, '.git', 'hooks', file_name)
105 self._check_hook_file_mode(file_path)
105 self._check_hook_file_mode(file_path)
106 self._check_hook_file_content(file_path, sys.executable)
106 self._check_hook_file_content(file_path, sys.executable)
107
107
108
108
109 class TestInstallGitHooks(BaseInstallHooks):
109 class TestInstallGitHooks(BaseInstallHooks):
110 HOOK_FILES = ('pre-receive', 'post-receive')
110 HOOK_FILES = ('pre-receive', 'post-receive')
111
111
112 def test_hooks_are_installed(self):
112 def test_hooks_are_installed(self):
113 repo = self.create_dummy_repo('git')
113 repo = self.create_dummy_repo('git')
114 result = hook_utils.install_git_hooks(repo.path, repo.bare)
114 result = hook_utils.install_git_hooks(repo.path, repo.bare)
115 assert result
115 assert result
116 self.check_hooks(repo.path, repo.bare)
116 self.check_hooks(repo.path, repo.bare)
117
117
118 def test_hooks_are_replaced(self):
118 def test_hooks_are_replaced(self):
119 repo = self.create_dummy_repo('git')
119 repo = self.create_dummy_repo('git')
120 hooks_path = os.path.join(repo.path, 'hooks')
120 hooks_path = os.path.join(repo.path, 'hooks')
121 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
121 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
122 self._create_fake_hook(
122 self._create_fake_hook(
123 file_path, content="RC_HOOK_VER = 'abcde'\n")
123 file_path, content="RC_HOOK_VER = 'abcde'\n")
124
124
125 result = hook_utils.install_git_hooks(repo.path, repo.bare)
125 result = hook_utils.install_git_hooks(repo.path, repo.bare)
126 assert result
126 assert result
127 self.check_hooks(repo.path, repo.bare)
127 self.check_hooks(repo.path, repo.bare)
128
128
129 def test_non_rc_hooks_are_not_replaced(self):
129 def test_non_rc_hooks_are_not_replaced(self):
130 repo = self.create_dummy_repo('git')
130 repo = self.create_dummy_repo('git')
131 hooks_path = os.path.join(repo.path, 'hooks')
131 hooks_path = os.path.join(repo.path, 'hooks')
132 non_rc_content = 'echo "non rc hook"\n'
132 non_rc_content = 'echo "non rc hook"\n'
133 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
133 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
134 self._create_fake_hook(
134 self._create_fake_hook(
135 file_path, content=non_rc_content)
135 file_path, content=non_rc_content)
136
136
137 result = hook_utils.install_git_hooks(repo.path, repo.bare)
137 result = hook_utils.install_git_hooks(repo.path, repo.bare)
138 assert result
138 assert result
139
139
140 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
140 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
141 with open(file_path, 'rt') as hook_file:
141 with open(file_path, 'rt') as hook_file:
142 content = hook_file.read()
142 content = hook_file.read()
143 assert content == non_rc_content
143 assert content == non_rc_content
144
144
145 def test_non_rc_hooks_are_replaced_with_force_flag(self):
145 def test_non_rc_hooks_are_replaced_with_force_flag(self):
146 repo = self.create_dummy_repo('git')
146 repo = self.create_dummy_repo('git')
147 hooks_path = os.path.join(repo.path, 'hooks')
147 hooks_path = os.path.join(repo.path, 'hooks')
148 non_rc_content = 'echo "non rc hook"\n'
148 non_rc_content = 'echo "non rc hook"\n'
149 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
149 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
150 self._create_fake_hook(
150 self._create_fake_hook(
151 file_path, content=non_rc_content)
151 file_path, content=non_rc_content)
152
152
153 result = hook_utils.install_git_hooks(
153 result = hook_utils.install_git_hooks(
154 repo.path, repo.bare, force_create=True)
154 repo.path, repo.bare, force_create=True)
155 assert result
155 assert result
156 self.check_hooks(repo.path, repo.bare)
156 self.check_hooks(repo.path, repo.bare)
157
157
158
158
159 class TestInstallSvnHooks(BaseInstallHooks):
159 class TestInstallSvnHooks(BaseInstallHooks):
160 HOOK_FILES = ('pre-commit', 'post-commit')
160 HOOK_FILES = ('pre-commit', 'post-commit')
161
161
162 def test_hooks_are_installed(self):
162 def test_hooks_are_installed(self):
163 repo = self.create_dummy_repo('svn')
163 repo = self.create_dummy_repo('svn')
164 result = hook_utils.install_svn_hooks(repo.path)
164 result = hook_utils.install_svn_hooks(repo.path)
165 assert result
165 assert result
166 self.check_hooks(repo.path)
166 self.check_hooks(repo.path)
167
167
168 def test_hooks_are_replaced(self):
168 def test_hooks_are_replaced(self):
169 repo = self.create_dummy_repo('svn')
169 repo = self.create_dummy_repo('svn')
170 hooks_path = os.path.join(repo.path, 'hooks')
170 hooks_path = os.path.join(repo.path, 'hooks')
171 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
171 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
172 self._create_fake_hook(
172 self._create_fake_hook(
173 file_path, content="RC_HOOK_VER = 'abcde'\n")
173 file_path, content="RC_HOOK_VER = 'abcde'\n")
174
174
175 result = hook_utils.install_svn_hooks(repo.path)
175 result = hook_utils.install_svn_hooks(repo.path)
176 assert result
176 assert result
177 self.check_hooks(repo.path)
177 self.check_hooks(repo.path)
178
178
179 def test_non_rc_hooks_are_not_replaced(self):
179 def test_non_rc_hooks_are_not_replaced(self):
180 repo = self.create_dummy_repo('svn')
180 repo = self.create_dummy_repo('svn')
181 hooks_path = os.path.join(repo.path, 'hooks')
181 hooks_path = os.path.join(repo.path, 'hooks')
182 non_rc_content = 'echo "non rc hook"\n'
182 non_rc_content = 'echo "non rc hook"\n'
183 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
183 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
184 self._create_fake_hook(
184 self._create_fake_hook(
185 file_path, content=non_rc_content)
185 file_path, content=non_rc_content)
186
186
187 result = hook_utils.install_svn_hooks(repo.path)
187 result = hook_utils.install_svn_hooks(repo.path)
188 assert result
188 assert result
189
189
190 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
190 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
191 with open(file_path, 'rt') as hook_file:
191 with open(file_path, 'rt') as hook_file:
192 content = hook_file.read()
192 content = hook_file.read()
193 assert content == non_rc_content
193 assert content == non_rc_content
194
194
195 def test_non_rc_hooks_are_replaced_with_force_flag(self):
195 def test_non_rc_hooks_are_replaced_with_force_flag(self):
196 repo = self.create_dummy_repo('svn')
196 repo = self.create_dummy_repo('svn')
197 hooks_path = os.path.join(repo.path, 'hooks')
197 hooks_path = os.path.join(repo.path, 'hooks')
198 non_rc_content = 'echo "non rc hook"\n'
198 non_rc_content = 'echo "non rc hook"\n'
199 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
199 for file_path in [os.path.join(hooks_path, f) for f in self.HOOK_FILES]:
200 self._create_fake_hook(
200 self._create_fake_hook(
201 file_path, content=non_rc_content)
201 file_path, content=non_rc_content)
202
202
203 result = hook_utils.install_svn_hooks(
203 result = hook_utils.install_svn_hooks(
204 repo.path, force_create=True)
204 repo.path, force_create=True)
205 assert result
205 assert result
206 self.check_hooks(repo.path, )
206 self.check_hooks(repo.path, )
@@ -1,112 +1,112 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2020 RhodeCode GmbH
2 # Copyright (C) 2014-2020 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
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 General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 import time
18 import time
19 import logging
19 import logging
20
20
21 import vcsserver
21 import vcsserver
22 from vcsserver.str_utils import safe_str, ascii_str
22 from vcsserver.str_utils import safe_str, ascii_str
23
23
24 log = logging.getLogger(__name__)
24 log = logging.getLogger(__name__)
25
25
26
26
27 def get_access_path(environ):
27 def get_access_path(environ):
28 path = environ.get('PATH_INFO')
28 path = environ.get('PATH_INFO')
29 return path
29 return path
30
30
31
31
32 def get_user_agent(environ):
32 def get_user_agent(environ):
33 return environ.get('HTTP_USER_AGENT')
33 return environ.get('HTTP_USER_AGENT')
34
34
35
35
36 def get_call_context(registry) -> dict:
36 def get_call_context(registry) -> dict:
37 cc = {}
37 cc = {}
38 if hasattr(registry, 'vcs_call_context'):
38 if hasattr(registry, 'vcs_call_context'):
39 cc.update({
39 cc.update({
40 'X-RC-Method': registry.vcs_call_context.get('method'),
40 'X-RC-Method': registry.vcs_call_context.get('method'),
41 'X-RC-Repo-Name': registry.vcs_call_context.get('repo_name')
41 'X-RC-Repo-Name': registry.vcs_call_context.get('repo_name')
42 })
42 })
43
43
44 return cc
44 return cc
45
45
46
46
47 class RequestWrapperTween(object):
47 class RequestWrapperTween(object):
48 def __init__(self, handler, registry):
48 def __init__(self, handler, registry):
49 self.handler = handler
49 self.handler = handler
50 self.registry = registry
50 self.registry = registry
51
51
52 # one-time configuration code goes here
52 # one-time configuration code goes here
53
53
54 def __call__(self, request):
54 def __call__(self, request):
55 start = time.time()
55 start = time.time()
56 log.debug('Starting request time measurement')
56 log.debug('Starting request time measurement')
57 response = None
57 response = None
58
58
59 try:
59 try:
60 response = self.handler(request)
60 response = self.handler(request)
61 finally:
61 finally:
62 ua = get_user_agent(request.environ)
62 ua = get_user_agent(request.environ)
63 call_context = get_call_context(request.registry)
63 call_context = get_call_context(request.registry)
64 vcs_method = call_context.get('X-RC-Method', '_NO_VCS_METHOD')
64 vcs_method = call_context.get('X-RC-Method', '_NO_VCS_METHOD')
65 repo_name = call_context.get('X-RC-Repo-Name', '')
65 repo_name = call_context.get('X-RC-Repo-Name', '')
66
66
67 count = request.request_count()
67 count = request.request_count()
68 _ver_ = ascii_str(vcsserver.__version__)
68 _ver_ = vcsserver.__version__
69 _path = safe_str(get_access_path(request.environ))
69 _path = safe_str(get_access_path(request.environ))
70
70
71 ip = '127.0.0.1'
71 ip = '127.0.0.1'
72 match_route = request.matched_route.name if request.matched_route else "NOT_FOUND"
72 match_route = request.matched_route.name if request.matched_route else "NOT_FOUND"
73 resp_code = getattr(response, 'status_code', 'UNDEFINED')
73 resp_code = getattr(response, 'status_code', 'UNDEFINED')
74
74
75 _view_path = f"{repo_name}@{_path}/{vcs_method}"
75 _view_path = f"{repo_name}@{_path}/{vcs_method}"
76
76
77 total = time.time() - start
77 total = time.time() - start
78
78
79 log.info(
79 log.info(
80 'Req[%4s] IP: %s %s Request to %s time: %.4fs [%s], VCSServer %s',
80 'Req[%4s] IP: %s %s Request to %s time: %.4fs [%s], VCSServer %s',
81 count, ip, request.environ.get('REQUEST_METHOD'),
81 count, ip, request.environ.get('REQUEST_METHOD'),
82 _view_path, total, ua, _ver_,
82 _view_path, total, ua, _ver_,
83 extra={"time": total, "ver": _ver_, "code": resp_code,
83 extra={"time": total, "ver": _ver_, "code": resp_code,
84 "path": _path, "view_name": match_route, "user_agent": ua,
84 "path": _path, "view_name": match_route, "user_agent": ua,
85 "vcs_method": vcs_method, "repo_name": repo_name}
85 "vcs_method": vcs_method, "repo_name": repo_name}
86 )
86 )
87
87
88 statsd = request.registry.statsd
88 statsd = request.registry.statsd
89 if statsd:
89 if statsd:
90 match_route = request.matched_route.name if request.matched_route else _path
90 match_route = request.matched_route.name if request.matched_route else _path
91 elapsed_time_ms = round(1000.0 * total) # use ms only
91 elapsed_time_ms = round(1000.0 * total) # use ms only
92 statsd.timing(
92 statsd.timing(
93 "vcsserver_req_timing.histogram", elapsed_time_ms,
93 "vcsserver_req_timing.histogram", elapsed_time_ms,
94 tags=[
94 tags=[
95 "view_name:{}".format(match_route),
95 "view_name:{}".format(match_route),
96 "code:{}".format(resp_code)
96 "code:{}".format(resp_code)
97 ],
97 ],
98 use_decimals=False
98 use_decimals=False
99 )
99 )
100 statsd.incr(
100 statsd.incr(
101 "vcsserver_req_total", tags=[
101 "vcsserver_req_total", tags=[
102 "view_name:{}".format(match_route),
102 "view_name:{}".format(match_route),
103 "code:{}".format(resp_code)
103 "code:{}".format(resp_code)
104 ])
104 ])
105
105
106 return response
106 return response
107
107
108
108
109 def includeme(config):
109 def includeme(config):
110 config.add_tween(
110 config.add_tween(
111 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
111 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
112 )
112 )
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now