##// END OF EJS Templates
subrepo: Apply mercurial sub repository patch.
Martin Bornhold -
r100:2582dee7 default
parent child Browse files
Show More
@@ -1,358 +1,359 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2016 RodeCode 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 base64
19 19 import locale
20 20 import logging
21 21 import uuid
22 22 import wsgiref.util
23 23 from itertools import chain
24 24
25 25 import msgpack
26 26 from beaker.cache import CacheManager
27 27 from beaker.util import parse_cache_config_options
28 28 from pyramid.config import Configurator
29 29 from pyramid.wsgi import wsgiapp
30 30
31 31 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
32 32 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
33 33 from vcsserver.echo_stub.echo_app import EchoApp
34 34 from vcsserver.exceptions import HTTPRepoLocked
35 35 from vcsserver.server import VcsServer
36 36
37 37 try:
38 38 from vcsserver.git import GitFactory, GitRemote
39 39 except ImportError:
40 40 GitFactory = None
41 41 GitRemote = None
42 42 try:
43 43 from vcsserver.hg import MercurialFactory, HgRemote
44 44 except ImportError:
45 45 MercurialFactory = None
46 46 HgRemote = None
47 47 try:
48 48 from vcsserver.svn import SubversionFactory, SvnRemote
49 49 except ImportError:
50 50 SubversionFactory = None
51 51 SvnRemote = None
52 52
53 53 log = logging.getLogger(__name__)
54 54
55 55
56 56 class VCS(object):
57 57 def __init__(self, locale=None, cache_config=None):
58 58 self.locale = locale
59 59 self.cache_config = cache_config
60 60 self._configure_locale()
61 61 self._initialize_cache()
62 62
63 63 if GitFactory and GitRemote:
64 64 git_repo_cache = self.cache.get_cache_region(
65 65 'git', region='repo_object')
66 66 git_factory = GitFactory(git_repo_cache)
67 67 self._git_remote = GitRemote(git_factory)
68 68 else:
69 69 log.info("Git client import failed")
70 70
71 71 if MercurialFactory and HgRemote:
72 72 hg_repo_cache = self.cache.get_cache_region(
73 73 'hg', region='repo_object')
74 74 hg_factory = MercurialFactory(hg_repo_cache)
75 75 self._hg_remote = HgRemote(hg_factory)
76 76 else:
77 77 log.info("Mercurial client import failed")
78 78
79 79 if SubversionFactory and SvnRemote:
80 80 svn_repo_cache = self.cache.get_cache_region(
81 81 'svn', region='repo_object')
82 82 svn_factory = SubversionFactory(svn_repo_cache)
83 83 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
84 84 else:
85 85 log.info("Subversion client import failed")
86 86
87 87 self._vcsserver = VcsServer()
88 88
89 89 def _initialize_cache(self):
90 90 cache_config = parse_cache_config_options(self.cache_config)
91 91 log.info('Initializing beaker cache: %s' % cache_config)
92 92 self.cache = CacheManager(**cache_config)
93 93
94 94 def _configure_locale(self):
95 95 if self.locale:
96 96 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
97 97 else:
98 98 log.info(
99 99 'Configuring locale subsystem based on environment variables')
100 100 try:
101 101 # If self.locale is the empty string, then the locale
102 102 # module will use the environment variables. See the
103 103 # documentation of the package `locale`.
104 104 locale.setlocale(locale.LC_ALL, self.locale)
105 105
106 106 language_code, encoding = locale.getlocale()
107 107 log.info(
108 108 'Locale set to language code "%s" with encoding "%s".',
109 109 language_code, encoding)
110 110 except locale.Error:
111 111 log.exception(
112 112 'Cannot set locale, not configuring the locale system')
113 113
114 114
115 115 class WsgiProxy(object):
116 116 def __init__(self, wsgi):
117 117 self.wsgi = wsgi
118 118
119 119 def __call__(self, environ, start_response):
120 120 input_data = environ['wsgi.input'].read()
121 121 input_data = msgpack.unpackb(input_data)
122 122
123 123 error = None
124 124 try:
125 125 data, status, headers = self.wsgi.handle(
126 126 input_data['environment'], input_data['input_data'],
127 127 *input_data['args'], **input_data['kwargs'])
128 128 except Exception as e:
129 129 data, status, headers = [], None, None
130 130 error = {
131 131 'message': str(e),
132 132 '_vcs_kind': getattr(e, '_vcs_kind', None)
133 133 }
134 134
135 135 start_response(200, {})
136 136 return self._iterator(error, status, headers, data)
137 137
138 138 def _iterator(self, error, status, headers, data):
139 139 initial_data = [
140 140 error,
141 141 status,
142 142 headers,
143 143 ]
144 144
145 145 for d in chain(initial_data, data):
146 146 yield msgpack.packb(d)
147 147
148 148
149 149 class HTTPApplication(object):
150 150 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
151 151
152 152 remote_wsgi = remote_wsgi
153 153 _use_echo_app = False
154 154
155 155 def __init__(self, settings=None):
156 156 self.config = Configurator(settings=settings)
157 157 locale = settings.get('', 'en_US.UTF-8')
158 158 vcs = VCS(locale=locale, cache_config=settings)
159 159 self._remotes = {
160 160 'hg': vcs._hg_remote,
161 161 'git': vcs._git_remote,
162 162 'svn': vcs._svn_remote,
163 163 'server': vcs._vcsserver,
164 164 }
165 165 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
166 166 self._use_echo_app = True
167 167 log.warning("Using EchoApp for VCS operations.")
168 168 self.remote_wsgi = remote_wsgi_stub
169 169 self._configure_settings(settings)
170 170 self._configure()
171 171
172 172 def _configure_settings(self, app_settings):
173 173 """
174 174 Configure the settings module.
175 175 """
176 176 git_path = app_settings.get('git_path', None)
177 177 if git_path:
178 178 settings.GIT_EXECUTABLE = git_path
179 179
180 180 def _configure(self):
181 181 self.config.add_renderer(
182 182 name='msgpack',
183 183 factory=self._msgpack_renderer_factory)
184 184
185 185 self.config.add_route('status', '/status')
186 186 self.config.add_route('hg_proxy', '/proxy/hg')
187 187 self.config.add_route('git_proxy', '/proxy/git')
188 188 self.config.add_route('vcs', '/{backend}')
189 189 self.config.add_route('stream_git', '/stream/git/*repo_name')
190 190 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
191 191
192 192 self.config.add_view(
193 193 self.status_view, route_name='status', renderer='json')
194 194 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
195 195 self.config.add_view(self.git_proxy(), route_name='git_proxy')
196 196 self.config.add_view(
197 197 self.vcs_view, route_name='vcs', renderer='msgpack')
198 198
199 199 self.config.add_view(self.hg_stream(), route_name='stream_hg')
200 200 self.config.add_view(self.git_stream(), route_name='stream_git')
201 201 self.config.add_view(
202 202 self.handle_vcs_exception, context=Exception,
203 203 custom_predicates=[self.is_vcs_exception])
204 204
205 205 def wsgi_app(self):
206 206 return self.config.make_wsgi_app()
207 207
208 208 def vcs_view(self, request):
209 209 remote = self._remotes[request.matchdict['backend']]
210 210 payload = msgpack.unpackb(request.body, use_list=True)
211 211 method = payload.get('method')
212 212 params = payload.get('params')
213 213 wire = params.get('wire')
214 214 args = params.get('args')
215 215 kwargs = params.get('kwargs')
216 216 if wire:
217 217 try:
218 218 wire['context'] = uuid.UUID(wire['context'])
219 219 except KeyError:
220 220 pass
221 221 args.insert(0, wire)
222 222
223 223 try:
224 224 resp = getattr(remote, method)(*args, **kwargs)
225 225 except Exception as e:
226 226 type_ = e.__class__.__name__
227 227 if type_ not in self.ALLOWED_EXCEPTIONS:
228 228 type_ = None
229 229
230 230 resp = {
231 231 'id': payload.get('id'),
232 232 'error': {
233 233 'message': e.message,
234 234 'type': type_
235 235 }
236 236 }
237 237 try:
238 238 resp['error']['_vcs_kind'] = e._vcs_kind
239 239 except AttributeError:
240 240 pass
241 241 else:
242 242 resp = {
243 243 'id': payload.get('id'),
244 244 'result': resp
245 245 }
246 246
247 247 return resp
248 248
249 249 def status_view(self, request):
250 250 return {'status': 'OK'}
251 251
252 252 def _msgpack_renderer_factory(self, info):
253 253 def _render(value, system):
254 254 value = msgpack.packb(value)
255 255 request = system.get('request')
256 256 if request is not None:
257 257 response = request.response
258 258 ct = response.content_type
259 259 if ct == response.default_content_type:
260 260 response.content_type = 'application/x-msgpack'
261 261 return value
262 262 return _render
263 263
264 264 def hg_proxy(self):
265 265 @wsgiapp
266 266 def _hg_proxy(environ, start_response):
267 267 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
268 268 return app(environ, start_response)
269 269 return _hg_proxy
270 270
271 271 def git_proxy(self):
272 272 @wsgiapp
273 273 def _git_proxy(environ, start_response):
274 274 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
275 275 return app(environ, start_response)
276 276 return _git_proxy
277 277
278 278 def hg_stream(self):
279 279 if self._use_echo_app:
280 280 @wsgiapp
281 281 def _hg_stream(environ, start_response):
282 282 app = EchoApp('fake_path', 'fake_name', None)
283 283 return app(environ, start_response)
284 284 return _hg_stream
285 285 else:
286 286 @wsgiapp
287 287 def _hg_stream(environ, start_response):
288 288 repo_path = environ['HTTP_X_RC_REPO_PATH']
289 289 repo_name = environ['HTTP_X_RC_REPO_NAME']
290 290 packed_config = base64.b64decode(
291 291 environ['HTTP_X_RC_REPO_CONFIG'])
292 292 config = msgpack.unpackb(packed_config)
293 293 app = scm_app.create_hg_wsgi_app(
294 294 repo_path, repo_name, config)
295 295
296 296 # Consitent path information for hgweb
297 297 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
298 298 environ['REPO_NAME'] = repo_name
299 299 return app(environ, ResponseFilter(start_response))
300 300 return _hg_stream
301 301
302 302 def git_stream(self):
303 303 if self._use_echo_app:
304 304 @wsgiapp
305 305 def _git_stream(environ, start_response):
306 306 app = EchoApp('fake_path', 'fake_name', None)
307 307 return app(environ, start_response)
308 308 return _git_stream
309 309 else:
310 310 @wsgiapp
311 311 def _git_stream(environ, start_response):
312 312 repo_path = environ['HTTP_X_RC_REPO_PATH']
313 313 repo_name = environ['HTTP_X_RC_REPO_NAME']
314 314 packed_config = base64.b64decode(
315 315 environ['HTTP_X_RC_REPO_CONFIG'])
316 316 config = msgpack.unpackb(packed_config)
317 317
318 318 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
319 319 app = scm_app.create_git_wsgi_app(
320 320 repo_path, repo_name, config)
321 321 return app(environ, start_response)
322 322 return _git_stream
323 323
324 324 def is_vcs_exception(self, context, request):
325 325 """
326 326 View predicate that returns true if the context object is a VCS
327 327 exception.
328 328 """
329 329 return hasattr(context, '_vcs_kind')
330 330
331 331 def handle_vcs_exception(self, exception, request):
332 332 if exception._vcs_kind == 'repo_locked':
333 333 # Get custom repo-locked status code if present.
334 334 status_code = request.headers.get('X-RC-Locked-Status-Code')
335 335 return HTTPRepoLocked(
336 336 title=exception.message, status_code=status_code)
337 337
338 338 # Re-raise exception if we can not handle it.
339 339 raise exception
340 340
341 341
342 342 class ResponseFilter(object):
343 343
344 344 def __init__(self, start_response):
345 345 self._start_response = start_response
346 346
347 347 def __call__(self, status, response_headers, exc_info=None):
348 348 headers = tuple(
349 349 (h, v) for h, v in response_headers
350 350 if not wsgiref.util.is_hop_by_hop(h))
351 351 return self._start_response(status, headers, exc_info)
352 352
353 353
354 354 def main(global_config, **settings):
355 355 if MercurialFactory:
356 356 hgpatches.patch_largefiles_capabilities()
357 hgpatches.patch_subrepo_type_mapping()
357 358 app = HTTPApplication(settings=settings)
358 359 return app.wsgi_app()
@@ -1,507 +1,508 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2016 RodeCode 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 atexit
19 19 import locale
20 20 import logging
21 21 import optparse
22 22 import os
23 23 import textwrap
24 24 import threading
25 25 import sys
26 26
27 27 import configobj
28 28 import Pyro4
29 29 from beaker.cache import CacheManager
30 30 from beaker.util import parse_cache_config_options
31 31
32 32 try:
33 33 from vcsserver.git import GitFactory, GitRemote
34 34 except ImportError:
35 35 GitFactory = None
36 36 GitRemote = None
37 37 try:
38 38 from vcsserver.hg import MercurialFactory, HgRemote
39 39 except ImportError:
40 40 MercurialFactory = None
41 41 HgRemote = None
42 42 try:
43 43 from vcsserver.svn import SubversionFactory, SvnRemote
44 44 except ImportError:
45 45 SubversionFactory = None
46 46 SvnRemote = None
47 47
48 48 from server import VcsServer
49 49 from vcsserver import hgpatches, remote_wsgi, settings
50 50 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
51 51
52 52 log = logging.getLogger(__name__)
53 53
54 54 HERE = os.path.dirname(os.path.abspath(__file__))
55 55 SERVER_RUNNING_FILE = None
56 56
57 57
58 58 # HOOKS - inspired by gunicorn #
59 59
60 60 def when_ready(server):
61 61 """
62 62 Called just after the server is started.
63 63 """
64 64
65 65 def _remove_server_running_file():
66 66 if os.path.isfile(SERVER_RUNNING_FILE):
67 67 os.remove(SERVER_RUNNING_FILE)
68 68
69 69 # top up to match to level location
70 70 if SERVER_RUNNING_FILE:
71 71 with open(SERVER_RUNNING_FILE, 'wb') as f:
72 72 f.write(str(os.getpid()))
73 73 # register cleanup of that file when server exits
74 74 atexit.register(_remove_server_running_file)
75 75
76 76
77 77 class LazyWriter(object):
78 78 """
79 79 File-like object that opens a file lazily when it is first written
80 80 to.
81 81 """
82 82
83 83 def __init__(self, filename, mode='w'):
84 84 self.filename = filename
85 85 self.fileobj = None
86 86 self.lock = threading.Lock()
87 87 self.mode = mode
88 88
89 89 def open(self):
90 90 if self.fileobj is None:
91 91 with self.lock:
92 92 self.fileobj = open(self.filename, self.mode)
93 93 return self.fileobj
94 94
95 95 def close(self):
96 96 fileobj = self.fileobj
97 97 if fileobj is not None:
98 98 fileobj.close()
99 99
100 100 def __del__(self):
101 101 self.close()
102 102
103 103 def write(self, text):
104 104 fileobj = self.open()
105 105 fileobj.write(text)
106 106 fileobj.flush()
107 107
108 108 def writelines(self, text):
109 109 fileobj = self.open()
110 110 fileobj.writelines(text)
111 111 fileobj.flush()
112 112
113 113 def flush(self):
114 114 self.open().flush()
115 115
116 116
117 117 class Application(object):
118 118 """
119 119 Represents the vcs server application.
120 120
121 121 This object is responsible to initialize the application and all needed
122 122 libraries. After that it hooks together the different objects and provides
123 123 them a way to access things like configuration.
124 124 """
125 125
126 126 def __init__(
127 127 self, host, port=None, locale='', threadpool_size=None,
128 128 timeout=None, cache_config=None, remote_wsgi_=None):
129 129
130 130 self.host = host
131 131 self.port = int(port) or settings.PYRO_PORT
132 132 self.threadpool_size = (
133 133 int(threadpool_size) if threadpool_size else None)
134 134 self.locale = locale
135 135 self.timeout = timeout
136 136 self.cache_config = cache_config
137 137 self.remote_wsgi = remote_wsgi_ or remote_wsgi
138 138
139 139 def init(self):
140 140 """
141 141 Configure and hook together all relevant objects.
142 142 """
143 143 self._configure_locale()
144 144 self._configure_pyro()
145 145 self._initialize_cache()
146 146 self._create_daemon_and_remote_objects(host=self.host, port=self.port)
147 147
148 148 def run(self):
149 149 """
150 150 Start the main loop of the application.
151 151 """
152 152
153 153 if hasattr(os, 'getpid'):
154 154 log.info('Starting %s in PID %i.', __name__, os.getpid())
155 155 else:
156 156 log.info('Starting %s.', __name__)
157 157 if SERVER_RUNNING_FILE:
158 158 log.info('PID file written as %s', SERVER_RUNNING_FILE)
159 159 else:
160 160 log.info('No PID file written by default.')
161 161 when_ready(self)
162 162 try:
163 163 self._pyrodaemon.requestLoop(
164 164 loopCondition=lambda: not self._vcsserver._shutdown)
165 165 finally:
166 166 self._pyrodaemon.shutdown()
167 167
168 168 def _configure_locale(self):
169 169 if self.locale:
170 170 log.info('Settings locale: `LC_ALL` to %s' % self.locale)
171 171 else:
172 172 log.info(
173 173 'Configuring locale subsystem based on environment variables')
174 174
175 175 try:
176 176 # If self.locale is the empty string, then the locale
177 177 # module will use the environment variables. See the
178 178 # documentation of the package `locale`.
179 179 locale.setlocale(locale.LC_ALL, self.locale)
180 180
181 181 language_code, encoding = locale.getlocale()
182 182 log.info(
183 183 'Locale set to language code "%s" with encoding "%s".',
184 184 language_code, encoding)
185 185 except locale.Error:
186 186 log.exception(
187 187 'Cannot set locale, not configuring the locale system')
188 188
189 189 def _configure_pyro(self):
190 190 if self.threadpool_size is not None:
191 191 log.info("Threadpool size set to %s", self.threadpool_size)
192 192 Pyro4.config.THREADPOOL_SIZE = self.threadpool_size
193 193 if self.timeout not in (None, 0, 0.0, '0'):
194 194 log.info("Timeout for RPC calls set to %s seconds", self.timeout)
195 195 Pyro4.config.COMMTIMEOUT = float(self.timeout)
196 196 Pyro4.config.SERIALIZER = 'pickle'
197 197 Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle')
198 198 Pyro4.config.SOCK_REUSE = True
199 199 # Uncomment the next line when you need to debug remote errors
200 200 # Pyro4.config.DETAILED_TRACEBACK = True
201 201
202 202 def _initialize_cache(self):
203 203 cache_config = parse_cache_config_options(self.cache_config)
204 204 log.info('Initializing beaker cache: %s' % cache_config)
205 205 self.cache = CacheManager(**cache_config)
206 206
207 207 def _create_daemon_and_remote_objects(self, host='localhost',
208 208 port=settings.PYRO_PORT):
209 209 daemon = Pyro4.Daemon(host=host, port=port)
210 210
211 211 self._vcsserver = VcsServer()
212 212 uri = daemon.register(
213 213 self._vcsserver, objectId=settings.PYRO_VCSSERVER)
214 214 log.info("Object registered = %s", uri)
215 215
216 216 if GitFactory and GitRemote:
217 217 git_repo_cache = self.cache.get_cache_region('git', region='repo_object')
218 218 git_factory = GitFactory(git_repo_cache)
219 219 self._git_remote = GitRemote(git_factory)
220 220 uri = daemon.register(self._git_remote, objectId=settings.PYRO_GIT)
221 221 log.info("Object registered = %s", uri)
222 222 else:
223 223 log.info("Git client import failed")
224 224
225 225 if MercurialFactory and HgRemote:
226 226 hg_repo_cache = self.cache.get_cache_region('hg', region='repo_object')
227 227 hg_factory = MercurialFactory(hg_repo_cache)
228 228 self._hg_remote = HgRemote(hg_factory)
229 229 uri = daemon.register(self._hg_remote, objectId=settings.PYRO_HG)
230 230 log.info("Object registered = %s", uri)
231 231 else:
232 232 log.info("Mercurial client import failed")
233 233
234 234 if SubversionFactory and SvnRemote:
235 235 svn_repo_cache = self.cache.get_cache_region('svn', region='repo_object')
236 236 svn_factory = SubversionFactory(svn_repo_cache)
237 237 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
238 238 uri = daemon.register(self._svn_remote, objectId=settings.PYRO_SVN)
239 239 log.info("Object registered = %s", uri)
240 240 else:
241 241 log.info("Subversion client import failed")
242 242
243 243 self._git_remote_wsgi = self.remote_wsgi.GitRemoteWsgi()
244 244 uri = daemon.register(self._git_remote_wsgi,
245 245 objectId=settings.PYRO_GIT_REMOTE_WSGI)
246 246 log.info("Object registered = %s", uri)
247 247
248 248 self._hg_remote_wsgi = self.remote_wsgi.HgRemoteWsgi()
249 249 uri = daemon.register(self._hg_remote_wsgi,
250 250 objectId=settings.PYRO_HG_REMOTE_WSGI)
251 251 log.info("Object registered = %s", uri)
252 252
253 253 self._pyrodaemon = daemon
254 254
255 255
256 256 class VcsServerCommand(object):
257 257
258 258 usage = '%prog'
259 259 description = """
260 260 Runs the VCS server
261 261 """
262 262 default_verbosity = 1
263 263
264 264 parser = optparse.OptionParser(
265 265 usage,
266 266 description=textwrap.dedent(description)
267 267 )
268 268 parser.add_option(
269 269 '--host',
270 270 type="str",
271 271 dest="host",
272 272 )
273 273 parser.add_option(
274 274 '--port',
275 275 type="int",
276 276 dest="port"
277 277 )
278 278 parser.add_option(
279 279 '--running-file',
280 280 dest='running_file',
281 281 metavar='RUNNING_FILE',
282 282 help="Create a running file after the server is initalized with "
283 283 "stored PID of process"
284 284 )
285 285 parser.add_option(
286 286 '--locale',
287 287 dest='locale',
288 288 help="Allows to set the locale, e.g. en_US.UTF-8",
289 289 default=""
290 290 )
291 291 parser.add_option(
292 292 '--log-file',
293 293 dest='log_file',
294 294 metavar='LOG_FILE',
295 295 help="Save output to the given log file (redirects stdout)"
296 296 )
297 297 parser.add_option(
298 298 '--log-level',
299 299 dest="log_level",
300 300 metavar="LOG_LEVEL",
301 301 help="use LOG_LEVEL to set log level "
302 302 "(debug,info,warning,error,critical)"
303 303 )
304 304 parser.add_option(
305 305 '--threadpool',
306 306 dest='threadpool_size',
307 307 type='int',
308 308 help="Set the size of the threadpool used to communicate with the "
309 309 "WSGI workers. This should be at least 6 times the number of "
310 310 "WSGI worker processes."
311 311 )
312 312 parser.add_option(
313 313 '--timeout',
314 314 dest='timeout',
315 315 type='float',
316 316 help="Set the timeout for RPC communication in seconds."
317 317 )
318 318 parser.add_option(
319 319 '--config',
320 320 dest='config_file',
321 321 type='string',
322 322 help="Configuration file for vcsserver."
323 323 )
324 324
325 325 def __init__(self, argv, quiet=False):
326 326 self.options, self.args = self.parser.parse_args(argv[1:])
327 327 if quiet:
328 328 self.options.verbose = 0
329 329
330 330 def _get_file_config(self):
331 331 ini_conf = {}
332 332 conf = configobj.ConfigObj(self.options.config_file)
333 333 if 'DEFAULT' in conf:
334 334 ini_conf = conf['DEFAULT']
335 335
336 336 return ini_conf
337 337
338 338 def _show_config(self, vcsserver_config):
339 339 order = [
340 340 'config_file',
341 341 'host',
342 342 'port',
343 343 'log_file',
344 344 'log_level',
345 345 'locale',
346 346 'threadpool_size',
347 347 'timeout',
348 348 'cache_config',
349 349 ]
350 350
351 351 def sorter(k):
352 352 return dict([(y, x) for x, y in enumerate(order)]).get(k)
353 353
354 354 _config = []
355 355 for k in sorted(vcsserver_config.keys(), key=sorter):
356 356 v = vcsserver_config[k]
357 357 # construct padded key for display eg %-20s % = key: val
358 358 k_formatted = ('%-'+str(len(max(order, key=len))+1)+'s') % (k+':')
359 359 _config.append(' * %s %s' % (k_formatted, v))
360 360 log.info('\n[vcsserver configuration]:\n'+'\n'.join(_config))
361 361
362 362 def _get_vcsserver_configuration(self):
363 363 _defaults = {
364 364 'config_file': None,
365 365 'git_path': 'git',
366 366 'host': 'localhost',
367 367 'port': settings.PYRO_PORT,
368 368 'log_file': None,
369 369 'log_level': 'debug',
370 370 'locale': None,
371 371 'threadpool_size': 16,
372 372 'timeout': None,
373 373
374 374 # Development support
375 375 'dev.use_echo_app': False,
376 376
377 377 # caches, baker style config
378 378 'beaker.cache.regions': 'repo_object',
379 379 'beaker.cache.repo_object.expire': '10',
380 380 'beaker.cache.repo_object.type': 'memory',
381 381 }
382 382 config = {}
383 383 config.update(_defaults)
384 384 # overwrite defaults with one loaded from file
385 385 config.update(self._get_file_config())
386 386
387 387 # overwrite with self.option which has the top priority
388 388 for k, v in self.options.__dict__.items():
389 389 if v or v == 0:
390 390 config[k] = v
391 391
392 392 # clear all "extra" keys if they are somehow passed,
393 393 # we only want defaults, so any extra stuff from self.options is cleared
394 394 # except beaker stuff which needs to be dynamic
395 395 for k in [k for k in config.copy().keys() if not k.startswith('beaker.cache.')]:
396 396 if k not in _defaults:
397 397 del config[k]
398 398
399 399 # group together the cache into one key.
400 400 # Needed further for beaker lib configuration
401 401 _k = {}
402 402 for k in [k for k in config.copy() if k.startswith('beaker.cache.')]:
403 403 _k[k] = config.pop(k)
404 404 config['cache_config'] = _k
405 405
406 406 return config
407 407
408 408 def out(self, msg): # pragma: no cover
409 409 if self.options.verbose > 0:
410 410 print(msg)
411 411
412 412 def run(self): # pragma: no cover
413 413 vcsserver_config = self._get_vcsserver_configuration()
414 414
415 415 # Ensure the log file is writeable
416 416 if vcsserver_config['log_file']:
417 417 stdout_log = self._configure_logfile()
418 418 else:
419 419 stdout_log = None
420 420
421 421 # set PID file with running lock
422 422 if self.options.running_file:
423 423 global SERVER_RUNNING_FILE
424 424 SERVER_RUNNING_FILE = self.options.running_file
425 425
426 426 # configure logging, and logging based on configuration file
427 427 self._configure_logging(level=vcsserver_config['log_level'],
428 428 stream=stdout_log)
429 429 if self.options.config_file:
430 430 if not os.path.isfile(self.options.config_file):
431 431 raise OSError('File %s does not exist' %
432 432 self.options.config_file)
433 433
434 434 self._configure_file_logging(self.options.config_file)
435 435
436 436 self._configure_settings(vcsserver_config)
437 437
438 438 # display current configuration of vcsserver
439 439 self._show_config(vcsserver_config)
440 440
441 441 if not vcsserver_config['dev.use_echo_app']:
442 442 remote_wsgi_mod = remote_wsgi
443 443 else:
444 444 log.warning("Using EchoApp for VCS endpoints.")
445 445 remote_wsgi_mod = remote_wsgi_stub
446 446
447 447 app = Application(
448 448 host=vcsserver_config['host'],
449 449 port=vcsserver_config['port'],
450 450 locale=vcsserver_config['locale'],
451 451 threadpool_size=vcsserver_config['threadpool_size'],
452 452 timeout=vcsserver_config['timeout'],
453 453 cache_config=vcsserver_config['cache_config'],
454 454 remote_wsgi_=remote_wsgi_mod)
455 455 app.init()
456 456 app.run()
457 457
458 458 def _configure_logging(self, level, stream=None):
459 459 _format = (
460 460 '%(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s')
461 461 levels = {
462 462 'debug': logging.DEBUG,
463 463 'info': logging.INFO,
464 464 'warning': logging.WARNING,
465 465 'error': logging.ERROR,
466 466 'critical': logging.CRITICAL,
467 467 }
468 468 try:
469 469 level = levels[level]
470 470 except KeyError:
471 471 raise AttributeError(
472 472 'Invalid log level please use one of %s' % (levels.keys(),))
473 473 logging.basicConfig(format=_format, stream=stream, level=level)
474 474 logging.getLogger('Pyro4').setLevel(level)
475 475
476 476 def _configure_file_logging(self, config):
477 477 import logging.config
478 478 try:
479 479 logging.config.fileConfig(config)
480 480 except Exception as e:
481 481 log.warning('Failed to configure logging based on given '
482 482 'config file. Error: %s' % e)
483 483
484 484 def _configure_logfile(self):
485 485 try:
486 486 writeable_log_file = open(self.options.log_file, 'a')
487 487 except IOError as ioe:
488 488 msg = 'Error: Unable to write to log file: %s' % ioe
489 489 raise ValueError(msg)
490 490 writeable_log_file.close()
491 491 stdout_log = LazyWriter(self.options.log_file, 'a')
492 492 sys.stdout = stdout_log
493 493 sys.stderr = stdout_log
494 494 return stdout_log
495 495
496 496 def _configure_settings(self, config):
497 497 """
498 498 Configure the settings module based on the given `config`.
499 499 """
500 500 settings.GIT_EXECUTABLE = config['git_path']
501 501
502 502
503 503 def main(argv=sys.argv, quiet=False):
504 504 if MercurialFactory:
505 505 hgpatches.patch_largefiles_capabilities()
506 hgpatches.patch_subrepo_type_mapping()
506 507 command = VcsServerCommand(argv, quiet=quiet)
507 508 return command.run()
General Comments 0
You need to be logged in to leave comments. Login now