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