##// END OF EJS Templates
fix(settings): fixed string:noquote parser, and make core.binary dir default better
super-admin -
r1217:68ec845c default
parent child Browse files
Show More
@@ -1,184 +1,185 b''
1 # Copyright (C) 2010-2023 RhodeCode GmbH
1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 #
2 #
3 # This program is free software: you can redistribute it and/or modify
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU Affero General Public License, version 3
4 # it under the terms of the GNU Affero General Public License, version 3
5 # (only), as published by the Free Software Foundation.
5 # (only), as published by the Free Software Foundation.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU Affero General Public License
12 # You should have received a copy of the GNU Affero General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 #
14 #
15 # This program is dual-licensed. If you wish to learn more about the
15 # This program is dual-licensed. If you wish to learn more about the
16 # RhodeCode Enterprise Edition, including its added features, Support services,
16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 import os
19 import os
20 import textwrap
20 import textwrap
21 import string
21 import string
22 import functools
22 import functools
23 import logging
23 import logging
24 import tempfile
24 import tempfile
25 import logging.config
25 import logging.config
26
26
27 from vcsserver.type_utils import str2bool, aslist
27 from vcsserver.type_utils import str2bool, aslist
28
28
29 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
30
30
31
31
32 # skip keys, that are set here, so we don't double process those
32 # skip keys, that are set here, so we don't double process those
33 set_keys = {
33 set_keys = {
34 '__file__': ''
34 '__file__': ''
35 }
35 }
36
36
37
37
38 class SettingsMaker:
38 class SettingsMaker:
39
39
40 def __init__(self, app_settings):
40 def __init__(self, app_settings):
41 self.settings = app_settings
41 self.settings = app_settings
42
42
43 @classmethod
43 @classmethod
44 def _bool_func(cls, input_val):
44 def _bool_func(cls, input_val):
45 if isinstance(input_val, bytes):
45 if isinstance(input_val, bytes):
46 # decode to str
46 # decode to str
47 input_val = input_val.decode('utf8')
47 input_val = input_val.decode('utf8')
48 return str2bool(input_val)
48 return str2bool(input_val)
49
49
50 @classmethod
50 @classmethod
51 def _int_func(cls, input_val):
51 def _int_func(cls, input_val):
52 return int(input_val)
52 return int(input_val)
53
53
54 @classmethod
54 @classmethod
55 def _float_func(cls, input_val):
55 def _float_func(cls, input_val):
56 return float(input_val)
56 return float(input_val)
57
57
58 @classmethod
58 @classmethod
59 def _list_func(cls, input_val, sep=','):
59 def _list_func(cls, input_val, sep=','):
60 return aslist(input_val, sep=sep)
60 return aslist(input_val, sep=sep)
61
61
62 @classmethod
62 @classmethod
63 def _string_func(cls, input_val, lower=True):
63 def _string_func(cls, input_val, lower=True):
64 if lower:
64 if lower:
65 input_val = input_val.lower()
65 input_val = input_val.lower()
66 return input_val
66 return input_val
67
67
68 @classmethod
68 @classmethod
69 def _string_no_quote_func(cls, input_val, lower=True):
69 def _string_no_quote_func(cls, input_val, lower=True):
70 """
70 """
71 Special case string function that detects if value is set to empty quote string
71 Special case string function that detects if value is set to empty quote string
72 e.g.
72 e.g.
73
73
74 core.binar_dir = ""
74 core.binary_dir = ""
75 """
75 """
76
76
77 input_val = cls._string_func(input_val, lower=lower)
77 input_val = cls._string_func(input_val, lower=lower)
78 if input_val in ['""', "''"]:
78 if input_val in ['""', "''"]:
79 return ''
79 return ''
80 return input_val
80
81
81 @classmethod
82 @classmethod
82 def _dir_func(cls, input_val, ensure_dir=False, mode=0o755):
83 def _dir_func(cls, input_val, ensure_dir=False, mode=0o755):
83
84
84 # ensure we have our dir created
85 # ensure we have our dir created
85 if not os.path.isdir(input_val) and ensure_dir:
86 if not os.path.isdir(input_val) and ensure_dir:
86 os.makedirs(input_val, mode=mode, exist_ok=True)
87 os.makedirs(input_val, mode=mode, exist_ok=True)
87
88
88 if not os.path.isdir(input_val):
89 if not os.path.isdir(input_val):
89 raise Exception(f'Dir at {input_val} does not exist')
90 raise Exception(f'Dir at {input_val} does not exist')
90 return input_val
91 return input_val
91
92
92 @classmethod
93 @classmethod
93 def _file_path_func(cls, input_val, ensure_dir=False, mode=0o755):
94 def _file_path_func(cls, input_val, ensure_dir=False, mode=0o755):
94 dirname = os.path.dirname(input_val)
95 dirname = os.path.dirname(input_val)
95 cls._dir_func(dirname, ensure_dir=ensure_dir)
96 cls._dir_func(dirname, ensure_dir=ensure_dir)
96 return input_val
97 return input_val
97
98
98 @classmethod
99 @classmethod
99 def _key_transformator(cls, key):
100 def _key_transformator(cls, key):
100 return "{}_{}".format('RC'.upper(), key.upper().replace('.', '_').replace('-', '_'))
101 return "{}_{}".format('RC'.upper(), key.upper().replace('.', '_').replace('-', '_'))
101
102
102 def maybe_env_key(self, key):
103 def maybe_env_key(self, key):
103 # now maybe we have this KEY in env, search and use the value with higher priority.
104 # now maybe we have this KEY in env, search and use the value with higher priority.
104 transformed_key = self._key_transformator(key)
105 transformed_key = self._key_transformator(key)
105 envvar_value = os.environ.get(transformed_key)
106 envvar_value = os.environ.get(transformed_key)
106 if envvar_value:
107 if envvar_value:
107 log.debug('using `%s` key instead of `%s` key for config', transformed_key, key)
108 log.debug('using `%s` key instead of `%s` key for config', transformed_key, key)
108
109
109 return envvar_value
110 return envvar_value
110
111
111 def env_expand(self):
112 def env_expand(self):
112 replaced = {}
113 replaced = {}
113 for k, v in self.settings.items():
114 for k, v in self.settings.items():
114 if k not in set_keys:
115 if k not in set_keys:
115 envvar_value = self.maybe_env_key(k)
116 envvar_value = self.maybe_env_key(k)
116 if envvar_value:
117 if envvar_value:
117 replaced[k] = envvar_value
118 replaced[k] = envvar_value
118 set_keys[k] = envvar_value
119 set_keys[k] = envvar_value
119
120
120 # replace ALL keys updated
121 # replace ALL keys updated
121 self.settings.update(replaced)
122 self.settings.update(replaced)
122
123
123 def enable_logging(self, logging_conf=None, level='INFO', formatter='generic'):
124 def enable_logging(self, logging_conf=None, level='INFO', formatter='generic'):
124 """
125 """
125 Helper to enable debug on running instance
126 Helper to enable debug on running instance
126 :return:
127 :return:
127 """
128 """
128
129
129 if not str2bool(self.settings.get('logging.autoconfigure')):
130 if not str2bool(self.settings.get('logging.autoconfigure')):
130 log.info('logging configuration based on main .ini file')
131 log.info('logging configuration based on main .ini file')
131 return
132 return
132
133
133 if logging_conf is None:
134 if logging_conf is None:
134 logging_conf = self.settings.get('logging.logging_conf_file') or ''
135 logging_conf = self.settings.get('logging.logging_conf_file') or ''
135
136
136 if not os.path.isfile(logging_conf):
137 if not os.path.isfile(logging_conf):
137 log.error('Unable to setup logging based on %s, '
138 log.error('Unable to setup logging based on %s, '
138 'file does not exist.... specify path using logging.logging_conf_file= config setting. ', logging_conf)
139 'file does not exist.... specify path using logging.logging_conf_file= config setting. ', logging_conf)
139 return
140 return
140
141
141 with open(logging_conf, 'rt') as f:
142 with open(logging_conf, 'rt') as f:
142 ini_template = textwrap.dedent(f.read())
143 ini_template = textwrap.dedent(f.read())
143 ini_template = string.Template(ini_template).safe_substitute(
144 ini_template = string.Template(ini_template).safe_substitute(
144 RC_LOGGING_LEVEL=os.environ.get('RC_LOGGING_LEVEL', '') or level,
145 RC_LOGGING_LEVEL=os.environ.get('RC_LOGGING_LEVEL', '') or level,
145 RC_LOGGING_FORMATTER=os.environ.get('RC_LOGGING_FORMATTER', '') or formatter
146 RC_LOGGING_FORMATTER=os.environ.get('RC_LOGGING_FORMATTER', '') or formatter
146 )
147 )
147
148
148 with tempfile.NamedTemporaryFile(prefix='rc_logging_', suffix='.ini', delete=False) as f:
149 with tempfile.NamedTemporaryFile(prefix='rc_logging_', suffix='.ini', delete=False) as f:
149 log.info('Saved Temporary LOGGING config at %s', f.name)
150 log.info('Saved Temporary LOGGING config at %s', f.name)
150 f.write(ini_template)
151 f.write(ini_template)
151
152
152 logging.config.fileConfig(f.name)
153 logging.config.fileConfig(f.name)
153 os.remove(f.name)
154 os.remove(f.name)
154
155
155 def make_setting(self, key, default, lower=False, default_when_empty=False, parser=None):
156 def make_setting(self, key, default, lower=False, default_when_empty=False, parser=None):
156 input_val = self.settings.get(key, default)
157 input_val = self.settings.get(key, default)
157
158
158 if default_when_empty and not input_val:
159 if default_when_empty and not input_val:
159 # use default value when value is set in the config but it is empty
160 # use default value when value is set in the config but it is empty
160 input_val = default
161 input_val = default
161
162
162 parser_func = {
163 parser_func = {
163 'bool': self._bool_func,
164 'bool': self._bool_func,
164 'int': self._int_func,
165 'int': self._int_func,
165 'float': self._float_func,
166 'float': self._float_func,
166 'list': self._list_func,
167 'list': self._list_func,
167 'list:newline': functools.partial(self._list_func, sep='/n'),
168 'list:newline': functools.partial(self._list_func, sep='/n'),
168 'list:spacesep': functools.partial(self._list_func, sep=' '),
169 'list:spacesep': functools.partial(self._list_func, sep=' '),
169 'string': functools.partial(self._string_func, lower=lower),
170 'string': functools.partial(self._string_func, lower=lower),
170 'string:noquote': functools.partial(self._string_no_quote_func, lower=lower),
171 'string:noquote': functools.partial(self._string_no_quote_func, lower=lower),
171 'dir': self._dir_func,
172 'dir': self._dir_func,
172 'dir:ensured': functools.partial(self._dir_func, ensure_dir=True),
173 'dir:ensured': functools.partial(self._dir_func, ensure_dir=True),
173 'file': self._file_path_func,
174 'file': self._file_path_func,
174 'file:ensured': functools.partial(self._file_path_func, ensure_dir=True),
175 'file:ensured': functools.partial(self._file_path_func, ensure_dir=True),
175 None: lambda i: i
176 None: lambda i: i
176 }[parser]
177 }[parser]
177
178
178 envvar_value = self.maybe_env_key(key)
179 envvar_value = self.maybe_env_key(key)
179 if envvar_value:
180 if envvar_value:
180 input_val = envvar_value
181 input_val = envvar_value
181 set_keys[key] = input_val
182 set_keys[key] = input_val
182
183
183 self.settings[key] = parser_func(input_val)
184 self.settings[key] = parser_func(input_val)
184 return self.settings[key]
185 return self.settings[key]
@@ -1,777 +1,779 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-2023 RhodeCode GmbH
2 # Copyright (C) 2014-2023 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 platform
20 import platform
21 import sys
21 import sys
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 tempfile
27 import tempfile
28 import psutil
28 import psutil
29
29
30 from itertools import chain
30 from itertools import chain
31
31
32 import msgpack
32 import msgpack
33 import configparser
33 import configparser
34
34
35 from pyramid.config import Configurator
35 from pyramid.config import Configurator
36 from pyramid.wsgi import wsgiapp
36 from pyramid.wsgi import wsgiapp
37 from pyramid.response import Response
37 from pyramid.response import Response
38
38
39 from vcsserver.base import BytesEnvelope, BinaryEnvelope
39 from vcsserver.base import BytesEnvelope, BinaryEnvelope
40 from vcsserver.lib.rc_json import json
40 from vcsserver.lib.rc_json import json
41 from vcsserver.config.settings_maker import SettingsMaker
41 from vcsserver.config.settings_maker import SettingsMaker
42 from vcsserver.str_utils import safe_int
42 from vcsserver.str_utils import safe_int
43 from vcsserver.lib.statsd_client import StatsdClient
43 from vcsserver.lib.statsd_client import StatsdClient
44 from vcsserver.tweens.request_wrapper import get_headers_call_context
44 from vcsserver.tweens.request_wrapper import get_headers_call_context
45
45
46 import vcsserver
46 import vcsserver
47 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
47 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
48 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
48 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
49 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
49 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
50 from vcsserver.echo_stub.echo_app import EchoApp
50 from vcsserver.echo_stub.echo_app import EchoApp
51 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
51 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
52 from vcsserver.lib.exc_tracking import store_exception, format_exc
52 from vcsserver.lib.exc_tracking import store_exception, format_exc
53 from vcsserver.server import VcsServer
53 from vcsserver.server import VcsServer
54
54
55 strict_vcs = True
55 strict_vcs = True
56
56
57 git_import_err = None
57 git_import_err = None
58 try:
58 try:
59 from vcsserver.remote.git_remote import GitFactory, GitRemote
59 from vcsserver.remote.git_remote import GitFactory, GitRemote
60 except ImportError as e:
60 except ImportError as e:
61 GitFactory = None
61 GitFactory = None
62 GitRemote = None
62 GitRemote = None
63 git_import_err = e
63 git_import_err = e
64 if strict_vcs:
64 if strict_vcs:
65 raise
65 raise
66
66
67
67
68 hg_import_err = None
68 hg_import_err = None
69 try:
69 try:
70 from vcsserver.remote.hg_remote import MercurialFactory, HgRemote
70 from vcsserver.remote.hg_remote import MercurialFactory, HgRemote
71 except ImportError as e:
71 except ImportError as e:
72 MercurialFactory = None
72 MercurialFactory = None
73 HgRemote = None
73 HgRemote = None
74 hg_import_err = e
74 hg_import_err = e
75 if strict_vcs:
75 if strict_vcs:
76 raise
76 raise
77
77
78
78
79 svn_import_err = None
79 svn_import_err = None
80 try:
80 try:
81 from vcsserver.remote.svn_remote import SubversionFactory, SvnRemote
81 from vcsserver.remote.svn_remote import SubversionFactory, SvnRemote
82 except ImportError as e:
82 except ImportError as e:
83 SubversionFactory = None
83 SubversionFactory = None
84 SvnRemote = None
84 SvnRemote = None
85 svn_import_err = e
85 svn_import_err = e
86 if strict_vcs:
86 if strict_vcs:
87 raise
87 raise
88
88
89 log = logging.getLogger(__name__)
89 log = logging.getLogger(__name__)
90
90
91 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
91 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
92 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
92 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
93
93
94 try:
94 try:
95 locale.setlocale(locale.LC_ALL, '')
95 locale.setlocale(locale.LC_ALL, '')
96 except locale.Error as e:
96 except locale.Error as e:
97 log.error(
97 log.error(
98 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
98 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
99 os.environ['LC_ALL'] = 'C'
99 os.environ['LC_ALL'] = 'C'
100
100
101
101
102 def _is_request_chunked(environ):
102 def _is_request_chunked(environ):
103 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
103 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
104 return stream
104 return stream
105
105
106
106
107 def log_max_fd():
107 def log_max_fd():
108 try:
108 try:
109 maxfd = psutil.Process().rlimit(psutil.RLIMIT_NOFILE)[1]
109 maxfd = psutil.Process().rlimit(psutil.RLIMIT_NOFILE)[1]
110 log.info('Max file descriptors value: %s', maxfd)
110 log.info('Max file descriptors value: %s', maxfd)
111 except Exception:
111 except Exception:
112 pass
112 pass
113
113
114
114
115 class VCS:
115 class VCS:
116 def __init__(self, locale_conf=None, cache_config=None):
116 def __init__(self, locale_conf=None, cache_config=None):
117 self.locale = locale_conf
117 self.locale = locale_conf
118 self.cache_config = cache_config
118 self.cache_config = cache_config
119 self._configure_locale()
119 self._configure_locale()
120
120
121 log_max_fd()
121 log_max_fd()
122
122
123 if GitFactory and GitRemote:
123 if GitFactory and GitRemote:
124 git_factory = GitFactory()
124 git_factory = GitFactory()
125 self._git_remote = GitRemote(git_factory)
125 self._git_remote = GitRemote(git_factory)
126 else:
126 else:
127 log.error("Git client import failed: %s", git_import_err)
127 log.error("Git client import failed: %s", git_import_err)
128
128
129 if MercurialFactory and HgRemote:
129 if MercurialFactory and HgRemote:
130 hg_factory = MercurialFactory()
130 hg_factory = MercurialFactory()
131 self._hg_remote = HgRemote(hg_factory)
131 self._hg_remote = HgRemote(hg_factory)
132 else:
132 else:
133 log.error("Mercurial client import failed: %s", hg_import_err)
133 log.error("Mercurial client import failed: %s", hg_import_err)
134
134
135 if SubversionFactory and SvnRemote:
135 if SubversionFactory and SvnRemote:
136 svn_factory = SubversionFactory()
136 svn_factory = SubversionFactory()
137
137
138 # hg factory is used for svn url validation
138 # hg factory is used for svn url validation
139 hg_factory = MercurialFactory()
139 hg_factory = MercurialFactory()
140 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
140 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
141 else:
141 else:
142 log.error("Subversion client import failed: %s", svn_import_err)
142 log.error("Subversion client import failed: %s", svn_import_err)
143
143
144 self._vcsserver = VcsServer()
144 self._vcsserver = VcsServer()
145
145
146 def _configure_locale(self):
146 def _configure_locale(self):
147 if self.locale:
147 if self.locale:
148 log.info('Settings locale: `LC_ALL` to %s', self.locale)
148 log.info('Settings locale: `LC_ALL` to %s', self.locale)
149 else:
149 else:
150 log.info('Configuring locale subsystem based on environment variables')
150 log.info('Configuring locale subsystem based on environment variables')
151 try:
151 try:
152 # If self.locale is the empty string, then the locale
152 # If self.locale is the empty string, then the locale
153 # module will use the environment variables. See the
153 # module will use the environment variables. See the
154 # documentation of the package `locale`.
154 # documentation of the package `locale`.
155 locale.setlocale(locale.LC_ALL, self.locale)
155 locale.setlocale(locale.LC_ALL, self.locale)
156
156
157 language_code, encoding = locale.getlocale()
157 language_code, encoding = locale.getlocale()
158 log.info(
158 log.info(
159 'Locale set to language code "%s" with encoding "%s".',
159 'Locale set to language code "%s" with encoding "%s".',
160 language_code, encoding)
160 language_code, encoding)
161 except locale.Error:
161 except locale.Error:
162 log.exception('Cannot set locale, not configuring the locale system')
162 log.exception('Cannot set locale, not configuring the locale system')
163
163
164
164
165 class WsgiProxy:
165 class WsgiProxy:
166 def __init__(self, wsgi):
166 def __init__(self, wsgi):
167 self.wsgi = wsgi
167 self.wsgi = wsgi
168
168
169 def __call__(self, environ, start_response):
169 def __call__(self, environ, start_response):
170 input_data = environ['wsgi.input'].read()
170 input_data = environ['wsgi.input'].read()
171 input_data = msgpack.unpackb(input_data)
171 input_data = msgpack.unpackb(input_data)
172
172
173 error = None
173 error = None
174 try:
174 try:
175 data, status, headers = self.wsgi.handle(
175 data, status, headers = self.wsgi.handle(
176 input_data['environment'], input_data['input_data'],
176 input_data['environment'], input_data['input_data'],
177 *input_data['args'], **input_data['kwargs'])
177 *input_data['args'], **input_data['kwargs'])
178 except Exception as e:
178 except Exception as e:
179 data, status, headers = [], None, None
179 data, status, headers = [], None, None
180 error = {
180 error = {
181 'message': str(e),
181 'message': str(e),
182 '_vcs_kind': getattr(e, '_vcs_kind', None)
182 '_vcs_kind': getattr(e, '_vcs_kind', None)
183 }
183 }
184
184
185 start_response(200, {})
185 start_response(200, {})
186 return self._iterator(error, status, headers, data)
186 return self._iterator(error, status, headers, data)
187
187
188 def _iterator(self, error, status, headers, data):
188 def _iterator(self, error, status, headers, data):
189 initial_data = [
189 initial_data = [
190 error,
190 error,
191 status,
191 status,
192 headers,
192 headers,
193 ]
193 ]
194
194
195 for d in chain(initial_data, data):
195 for d in chain(initial_data, data):
196 yield msgpack.packb(d)
196 yield msgpack.packb(d)
197
197
198
198
199 def not_found(request):
199 def not_found(request):
200 return {'status': '404 NOT FOUND'}
200 return {'status': '404 NOT FOUND'}
201
201
202
202
203 class VCSViewPredicate:
203 class VCSViewPredicate:
204 def __init__(self, val, config):
204 def __init__(self, val, config):
205 self.remotes = val
205 self.remotes = val
206
206
207 def text(self):
207 def text(self):
208 return f'vcs view method = {list(self.remotes.keys())}'
208 return f'vcs view method = {list(self.remotes.keys())}'
209
209
210 phash = text
210 phash = text
211
211
212 def __call__(self, context, request):
212 def __call__(self, context, request):
213 """
213 """
214 View predicate that returns true if given backend is supported by
214 View predicate that returns true if given backend is supported by
215 defined remotes.
215 defined remotes.
216 """
216 """
217 backend = request.matchdict.get('backend')
217 backend = request.matchdict.get('backend')
218 return backend in self.remotes
218 return backend in self.remotes
219
219
220
220
221 class HTTPApplication:
221 class HTTPApplication:
222 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
222 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
223
223
224 remote_wsgi = remote_wsgi
224 remote_wsgi = remote_wsgi
225 _use_echo_app = False
225 _use_echo_app = False
226
226
227 def __init__(self, settings=None, global_config=None):
227 def __init__(self, settings=None, global_config=None):
228
228
229 self.config = Configurator(settings=settings)
229 self.config = Configurator(settings=settings)
230 # Init our statsd at very start
230 # Init our statsd at very start
231 self.config.registry.statsd = StatsdClient.statsd
231 self.config.registry.statsd = StatsdClient.statsd
232 self.config.registry.vcs_call_context = {}
232 self.config.registry.vcs_call_context = {}
233
233
234 self.global_config = global_config
234 self.global_config = global_config
235 self.config.include('vcsserver.lib.rc_cache')
235 self.config.include('vcsserver.lib.rc_cache')
236 self.config.include('vcsserver.lib.rc_cache.archive_cache')
236 self.config.include('vcsserver.lib.rc_cache.archive_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 binary_dir = app_settings['core.binary_dir']
262 binary_dir = app_settings['core.binary_dir']
263
263
264 settings.BINARY_DIR = binary_dir
264 settings.BINARY_DIR = binary_dir
265
265
266 # from core.binary dir we set executable paths
266 # from core.binary dir we set executable paths
267 settings.GIT_EXECUTABLE = os.path.join(binary_dir, settings.GIT_EXECUTABLE)
267 settings.GIT_EXECUTABLE = os.path.join(binary_dir, settings.GIT_EXECUTABLE)
268 settings.SVN_EXECUTABLE = os.path.join(binary_dir, settings.SVN_EXECUTABLE)
268 settings.SVN_EXECUTABLE = os.path.join(binary_dir, settings.SVN_EXECUTABLE)
269 settings.SVNLOOK_EXECUTABLE = os.path.join(binary_dir, settings.SVNLOOK_EXECUTABLE)
269 settings.SVNLOOK_EXECUTABLE = os.path.join(binary_dir, settings.SVNLOOK_EXECUTABLE)
270
270
271 # Store the settings to make them available to other modules.
271 # Store the settings to make them available to other modules.
272 vcsserver.PYRAMID_SETTINGS = settings_merged
272 vcsserver.PYRAMID_SETTINGS = settings_merged
273 vcsserver.CONFIG = settings_merged
273 vcsserver.CONFIG = settings_merged
274
274
275 def _configure(self):
275 def _configure(self):
276 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
276 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
277
277
278 self.config.add_route('service', '/_service')
278 self.config.add_route('service', '/_service')
279 self.config.add_route('status', '/status')
279 self.config.add_route('status', '/status')
280 self.config.add_route('hg_proxy', '/proxy/hg')
280 self.config.add_route('hg_proxy', '/proxy/hg')
281 self.config.add_route('git_proxy', '/proxy/git')
281 self.config.add_route('git_proxy', '/proxy/git')
282
282
283 # rpc methods
283 # rpc methods
284 self.config.add_route('vcs', '/{backend}')
284 self.config.add_route('vcs', '/{backend}')
285
285
286 # streaming rpc remote methods
286 # streaming rpc remote methods
287 self.config.add_route('vcs_stream', '/{backend}/stream')
287 self.config.add_route('vcs_stream', '/{backend}/stream')
288
288
289 # vcs operations clone/push as streaming
289 # vcs operations clone/push as streaming
290 self.config.add_route('stream_git', '/stream/git/*repo_name')
290 self.config.add_route('stream_git', '/stream/git/*repo_name')
291 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
291 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
292
292
293 self.config.add_view(self.status_view, route_name='status', renderer='json')
293 self.config.add_view(self.status_view, route_name='status', renderer='json')
294 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
294 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
295
295
296 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
296 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
297 self.config.add_view(self.git_proxy(), route_name='git_proxy')
297 self.config.add_view(self.git_proxy(), route_name='git_proxy')
298 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
298 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
299 vcs_view=self._remotes)
299 vcs_view=self._remotes)
300 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
300 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
301 vcs_view=self._remotes)
301 vcs_view=self._remotes)
302
302
303 self.config.add_view(self.hg_stream(), route_name='stream_hg')
303 self.config.add_view(self.hg_stream(), route_name='stream_hg')
304 self.config.add_view(self.git_stream(), route_name='stream_git')
304 self.config.add_view(self.git_stream(), route_name='stream_git')
305
305
306 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
306 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
307
307
308 self.config.add_notfound_view(not_found, renderer='json')
308 self.config.add_notfound_view(not_found, renderer='json')
309
309
310 self.config.add_view(self.handle_vcs_exception, context=Exception)
310 self.config.add_view(self.handle_vcs_exception, context=Exception)
311
311
312 self.config.add_tween(
312 self.config.add_tween(
313 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
313 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
314 )
314 )
315 self.config.add_request_method(
315 self.config.add_request_method(
316 'vcsserver.lib.request_counter.get_request_counter',
316 'vcsserver.lib.request_counter.get_request_counter',
317 'request_count')
317 'request_count')
318
318
319 def wsgi_app(self):
319 def wsgi_app(self):
320 return self.config.make_wsgi_app()
320 return self.config.make_wsgi_app()
321
321
322 def _vcs_view_params(self, request):
322 def _vcs_view_params(self, request):
323 remote = self._remotes[request.matchdict['backend']]
323 remote = self._remotes[request.matchdict['backend']]
324 payload = msgpack.unpackb(request.body, use_list=True)
324 payload = msgpack.unpackb(request.body, use_list=True)
325
325
326 method = payload.get('method')
326 method = payload.get('method')
327 params = payload['params']
327 params = payload['params']
328 wire = params.get('wire')
328 wire = params.get('wire')
329 args = params.get('args')
329 args = params.get('args')
330 kwargs = params.get('kwargs')
330 kwargs = params.get('kwargs')
331 context_uid = None
331 context_uid = None
332
332
333 request.registry.vcs_call_context = {
333 request.registry.vcs_call_context = {
334 'method': method,
334 'method': method,
335 'repo_name': payload.get('_repo_name'),
335 'repo_name': payload.get('_repo_name'),
336 }
336 }
337
337
338 if wire:
338 if wire:
339 try:
339 try:
340 wire['context'] = context_uid = uuid.UUID(wire['context'])
340 wire['context'] = context_uid = uuid.UUID(wire['context'])
341 except KeyError:
341 except KeyError:
342 pass
342 pass
343 args.insert(0, wire)
343 args.insert(0, wire)
344 repo_state_uid = wire.get('repo_state_uid') if wire else None
344 repo_state_uid = wire.get('repo_state_uid') if wire else None
345
345
346 # NOTE(marcink): trading complexity for slight performance
346 # NOTE(marcink): trading complexity for slight performance
347 if log.isEnabledFor(logging.DEBUG):
347 if log.isEnabledFor(logging.DEBUG):
348 # also we SKIP printing out any of those methods args since they maybe excessive
348 # also we SKIP printing out any of those methods args since they maybe excessive
349 just_args_methods = {
349 just_args_methods = {
350 'commitctx': ('content', 'removed', 'updated'),
350 'commitctx': ('content', 'removed', 'updated'),
351 'commit': ('content', 'removed', 'updated')
351 'commit': ('content', 'removed', 'updated')
352 }
352 }
353 if method in just_args_methods:
353 if method in just_args_methods:
354 skip_args = just_args_methods[method]
354 skip_args = just_args_methods[method]
355 call_args = ''
355 call_args = ''
356 call_kwargs = {}
356 call_kwargs = {}
357 for k in kwargs:
357 for k in kwargs:
358 if k in skip_args:
358 if k in skip_args:
359 # replace our skip key with dummy
359 # replace our skip key with dummy
360 call_kwargs[k] = f'RemovedParam({k})'
360 call_kwargs[k] = f'RemovedParam({k})'
361 else:
361 else:
362 call_kwargs[k] = kwargs[k]
362 call_kwargs[k] = kwargs[k]
363 else:
363 else:
364 call_args = args[1:]
364 call_args = args[1:]
365 call_kwargs = kwargs
365 call_kwargs = kwargs
366
366
367 log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
367 log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
368 method, call_args, call_kwargs, context_uid, repo_state_uid)
368 method, call_args, call_kwargs, context_uid, repo_state_uid)
369
369
370 statsd = request.registry.statsd
370 statsd = request.registry.statsd
371 if statsd:
371 if statsd:
372 statsd.incr(
372 statsd.incr(
373 'vcsserver_method_total', tags=[
373 'vcsserver_method_total', tags=[
374 f"method:{method}",
374 f"method:{method}",
375 ])
375 ])
376 return payload, remote, method, args, kwargs
376 return payload, remote, method, args, kwargs
377
377
378 def vcs_view(self, request):
378 def vcs_view(self, request):
379
379
380 payload, remote, method, args, kwargs = self._vcs_view_params(request)
380 payload, remote, method, args, kwargs = self._vcs_view_params(request)
381 payload_id = payload.get('id')
381 payload_id = payload.get('id')
382
382
383 try:
383 try:
384 resp = getattr(remote, method)(*args, **kwargs)
384 resp = getattr(remote, method)(*args, **kwargs)
385 except Exception as e:
385 except Exception as e:
386 exc_info = list(sys.exc_info())
386 exc_info = list(sys.exc_info())
387 exc_type, exc_value, exc_traceback = exc_info
387 exc_type, exc_value, exc_traceback = exc_info
388
388
389 org_exc = getattr(e, '_org_exc', None)
389 org_exc = getattr(e, '_org_exc', None)
390 org_exc_name = None
390 org_exc_name = None
391 org_exc_tb = ''
391 org_exc_tb = ''
392 if org_exc:
392 if org_exc:
393 org_exc_name = org_exc.__class__.__name__
393 org_exc_name = org_exc.__class__.__name__
394 org_exc_tb = getattr(e, '_org_exc_tb', '')
394 org_exc_tb = getattr(e, '_org_exc_tb', '')
395 # replace our "faked" exception with our org
395 # replace our "faked" exception with our org
396 exc_info[0] = org_exc.__class__
396 exc_info[0] = org_exc.__class__
397 exc_info[1] = org_exc
397 exc_info[1] = org_exc
398
398
399 should_store_exc = True
399 should_store_exc = True
400 if org_exc:
400 if org_exc:
401 def get_exc_fqn(_exc_obj):
401 def get_exc_fqn(_exc_obj):
402 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
402 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
403 return module_name + '.' + org_exc_name
403 return module_name + '.' + org_exc_name
404
404
405 exc_fqn = get_exc_fqn(org_exc)
405 exc_fqn = get_exc_fqn(org_exc)
406
406
407 if exc_fqn in ['mercurial.error.RepoLookupError',
407 if exc_fqn in ['mercurial.error.RepoLookupError',
408 'vcsserver.exceptions.RefNotFoundException']:
408 'vcsserver.exceptions.RefNotFoundException']:
409 should_store_exc = False
409 should_store_exc = False
410
410
411 if should_store_exc:
411 if should_store_exc:
412 store_exception(id(exc_info), exc_info, request_path=request.path)
412 store_exception(id(exc_info), exc_info, request_path=request.path)
413
413
414 tb_info = format_exc(exc_info)
414 tb_info = format_exc(exc_info)
415
415
416 type_ = e.__class__.__name__
416 type_ = e.__class__.__name__
417 if type_ not in self.ALLOWED_EXCEPTIONS:
417 if type_ not in self.ALLOWED_EXCEPTIONS:
418 type_ = None
418 type_ = None
419
419
420 resp = {
420 resp = {
421 'id': payload_id,
421 'id': payload_id,
422 'error': {
422 'error': {
423 'message': str(e),
423 'message': str(e),
424 'traceback': tb_info,
424 'traceback': tb_info,
425 'org_exc': org_exc_name,
425 'org_exc': org_exc_name,
426 'org_exc_tb': org_exc_tb,
426 'org_exc_tb': org_exc_tb,
427 'type': type_
427 'type': type_
428 }
428 }
429 }
429 }
430
430
431 try:
431 try:
432 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
432 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
433 except AttributeError:
433 except AttributeError:
434 pass
434 pass
435 else:
435 else:
436 resp = {
436 resp = {
437 'id': payload_id,
437 'id': payload_id,
438 'result': resp
438 'result': resp
439 }
439 }
440 log.debug('Serving data for method %s', method)
440 log.debug('Serving data for method %s', method)
441 return resp
441 return resp
442
442
443 def vcs_stream_view(self, request):
443 def vcs_stream_view(self, request):
444 payload, remote, method, args, kwargs = self._vcs_view_params(request)
444 payload, remote, method, args, kwargs = self._vcs_view_params(request)
445 # this method has a stream: marker we remove it here
445 # this method has a stream: marker we remove it here
446 method = method.split('stream:')[-1]
446 method = method.split('stream:')[-1]
447 chunk_size = safe_int(payload.get('chunk_size')) or 4096
447 chunk_size = safe_int(payload.get('chunk_size')) or 4096
448
448
449 resp = getattr(remote, method)(*args, **kwargs)
449 resp = getattr(remote, method)(*args, **kwargs)
450
450
451 def get_chunked_data(method_resp):
451 def get_chunked_data(method_resp):
452 stream = io.BytesIO(method_resp)
452 stream = io.BytesIO(method_resp)
453 while 1:
453 while 1:
454 chunk = stream.read(chunk_size)
454 chunk = stream.read(chunk_size)
455 if not chunk:
455 if not chunk:
456 break
456 break
457 yield chunk
457 yield chunk
458
458
459 response = Response(app_iter=get_chunked_data(resp))
459 response = Response(app_iter=get_chunked_data(resp))
460 response.content_type = 'application/octet-stream'
460 response.content_type = 'application/octet-stream'
461
461
462 return response
462 return response
463
463
464 def status_view(self, request):
464 def status_view(self, request):
465 import vcsserver
465 import vcsserver
466 _platform_id = platform.uname()[1] or 'instance'
466 _platform_id = platform.uname()[1] or 'instance'
467
467
468 return {
468 return {
469 "status": "OK",
469 "status": "OK",
470 "vcsserver_version": vcsserver.get_version(),
470 "vcsserver_version": vcsserver.get_version(),
471 "platform": _platform_id,
471 "platform": _platform_id,
472 "pid": os.getpid(),
472 "pid": os.getpid(),
473 }
473 }
474
474
475 def service_view(self, request):
475 def service_view(self, request):
476 import vcsserver
476 import vcsserver
477
477
478 payload = msgpack.unpackb(request.body, use_list=True)
478 payload = msgpack.unpackb(request.body, use_list=True)
479 server_config, app_config = {}, {}
479 server_config, app_config = {}, {}
480
480
481 try:
481 try:
482 path = self.global_config['__file__']
482 path = self.global_config['__file__']
483 config = configparser.RawConfigParser()
483 config = configparser.RawConfigParser()
484
484
485 config.read(path)
485 config.read(path)
486
486
487 if config.has_section('server:main'):
487 if config.has_section('server:main'):
488 server_config = dict(config.items('server:main'))
488 server_config = dict(config.items('server:main'))
489 if config.has_section('app:main'):
489 if config.has_section('app:main'):
490 app_config = dict(config.items('app:main'))
490 app_config = dict(config.items('app:main'))
491
491
492 except Exception:
492 except Exception:
493 log.exception('Failed to read .ini file for display')
493 log.exception('Failed to read .ini file for display')
494
494
495 environ = list(os.environ.items())
495 environ = list(os.environ.items())
496
496
497 resp = {
497 resp = {
498 'id': payload.get('id'),
498 'id': payload.get('id'),
499 'result': dict(
499 'result': dict(
500 version=vcsserver.get_version(),
500 version=vcsserver.get_version(),
501 config=server_config,
501 config=server_config,
502 app_config=app_config,
502 app_config=app_config,
503 environ=environ,
503 environ=environ,
504 payload=payload,
504 payload=payload,
505 )
505 )
506 }
506 }
507 return resp
507 return resp
508
508
509 def _msgpack_renderer_factory(self, info):
509 def _msgpack_renderer_factory(self, info):
510
510
511 def _render(value, system):
511 def _render(value, system):
512 bin_type = False
512 bin_type = False
513 res = value.get('result')
513 res = value.get('result')
514 if isinstance(res, BytesEnvelope):
514 if isinstance(res, BytesEnvelope):
515 log.debug('Result is wrapped in BytesEnvelope type')
515 log.debug('Result is wrapped in BytesEnvelope type')
516 bin_type = True
516 bin_type = True
517 elif isinstance(res, BinaryEnvelope):
517 elif isinstance(res, BinaryEnvelope):
518 log.debug('Result is wrapped in BinaryEnvelope type')
518 log.debug('Result is wrapped in BinaryEnvelope type')
519 value['result'] = res.val
519 value['result'] = res.val
520 bin_type = True
520 bin_type = True
521
521
522 request = system.get('request')
522 request = system.get('request')
523 if request is not None:
523 if request is not None:
524 response = request.response
524 response = request.response
525 ct = response.content_type
525 ct = response.content_type
526 if ct == response.default_content_type:
526 if ct == response.default_content_type:
527 response.content_type = 'application/x-msgpack'
527 response.content_type = 'application/x-msgpack'
528 if bin_type:
528 if bin_type:
529 response.content_type = 'application/x-msgpack-bin'
529 response.content_type = 'application/x-msgpack-bin'
530
530
531 return msgpack.packb(value, use_bin_type=bin_type)
531 return msgpack.packb(value, use_bin_type=bin_type)
532 return _render
532 return _render
533
533
534 def set_env_from_config(self, environ, config):
534 def set_env_from_config(self, environ, config):
535 dict_conf = {}
535 dict_conf = {}
536 try:
536 try:
537 for elem in config:
537 for elem in config:
538 if elem[0] == 'rhodecode':
538 if elem[0] == 'rhodecode':
539 dict_conf = json.loads(elem[2])
539 dict_conf = json.loads(elem[2])
540 break
540 break
541 except Exception:
541 except Exception:
542 log.exception('Failed to fetch SCM CONFIG')
542 log.exception('Failed to fetch SCM CONFIG')
543 return
543 return
544
544
545 username = dict_conf.get('username')
545 username = dict_conf.get('username')
546 if username:
546 if username:
547 environ['REMOTE_USER'] = username
547 environ['REMOTE_USER'] = username
548 # mercurial specific, some extension api rely on this
548 # mercurial specific, some extension api rely on this
549 environ['HGUSER'] = username
549 environ['HGUSER'] = username
550
550
551 ip = dict_conf.get('ip')
551 ip = dict_conf.get('ip')
552 if ip:
552 if ip:
553 environ['REMOTE_HOST'] = ip
553 environ['REMOTE_HOST'] = ip
554
554
555 if _is_request_chunked(environ):
555 if _is_request_chunked(environ):
556 # set the compatibility flag for webob
556 # set the compatibility flag for webob
557 environ['wsgi.input_terminated'] = True
557 environ['wsgi.input_terminated'] = True
558
558
559 def hg_proxy(self):
559 def hg_proxy(self):
560 @wsgiapp
560 @wsgiapp
561 def _hg_proxy(environ, start_response):
561 def _hg_proxy(environ, start_response):
562 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
562 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
563 return app(environ, start_response)
563 return app(environ, start_response)
564 return _hg_proxy
564 return _hg_proxy
565
565
566 def git_proxy(self):
566 def git_proxy(self):
567 @wsgiapp
567 @wsgiapp
568 def _git_proxy(environ, start_response):
568 def _git_proxy(environ, start_response):
569 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
569 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
570 return app(environ, start_response)
570 return app(environ, start_response)
571 return _git_proxy
571 return _git_proxy
572
572
573 def hg_stream(self):
573 def hg_stream(self):
574 if self._use_echo_app:
574 if self._use_echo_app:
575 @wsgiapp
575 @wsgiapp
576 def _hg_stream(environ, start_response):
576 def _hg_stream(environ, start_response):
577 app = EchoApp('fake_path', 'fake_name', None)
577 app = EchoApp('fake_path', 'fake_name', None)
578 return app(environ, start_response)
578 return app(environ, start_response)
579 return _hg_stream
579 return _hg_stream
580 else:
580 else:
581 @wsgiapp
581 @wsgiapp
582 def _hg_stream(environ, start_response):
582 def _hg_stream(environ, start_response):
583 log.debug('http-app: handling hg stream')
583 log.debug('http-app: handling hg stream')
584 call_context = get_headers_call_context(environ)
584 call_context = get_headers_call_context(environ)
585
585
586 repo_path = call_context['repo_path']
586 repo_path = call_context['repo_path']
587 repo_name = call_context['repo_name']
587 repo_name = call_context['repo_name']
588 config = call_context['repo_config']
588 config = call_context['repo_config']
589
589
590 app = scm_app.create_hg_wsgi_app(
590 app = scm_app.create_hg_wsgi_app(
591 repo_path, repo_name, config)
591 repo_path, repo_name, config)
592
592
593 # Consistent path information for hgweb
593 # Consistent path information for hgweb
594 environ['PATH_INFO'] = call_context['path_info']
594 environ['PATH_INFO'] = call_context['path_info']
595 environ['REPO_NAME'] = repo_name
595 environ['REPO_NAME'] = repo_name
596 self.set_env_from_config(environ, config)
596 self.set_env_from_config(environ, config)
597
597
598 log.debug('http-app: starting app handler '
598 log.debug('http-app: starting app handler '
599 'with %s and process request', app)
599 'with %s and process request', app)
600 return app(environ, ResponseFilter(start_response))
600 return app(environ, ResponseFilter(start_response))
601 return _hg_stream
601 return _hg_stream
602
602
603 def git_stream(self):
603 def git_stream(self):
604 if self._use_echo_app:
604 if self._use_echo_app:
605 @wsgiapp
605 @wsgiapp
606 def _git_stream(environ, start_response):
606 def _git_stream(environ, start_response):
607 app = EchoApp('fake_path', 'fake_name', None)
607 app = EchoApp('fake_path', 'fake_name', None)
608 return app(environ, start_response)
608 return app(environ, start_response)
609 return _git_stream
609 return _git_stream
610 else:
610 else:
611 @wsgiapp
611 @wsgiapp
612 def _git_stream(environ, start_response):
612 def _git_stream(environ, start_response):
613 log.debug('http-app: handling git stream')
613 log.debug('http-app: handling git stream')
614
614
615 call_context = get_headers_call_context(environ)
615 call_context = get_headers_call_context(environ)
616
616
617 repo_path = call_context['repo_path']
617 repo_path = call_context['repo_path']
618 repo_name = call_context['repo_name']
618 repo_name = call_context['repo_name']
619 config = call_context['repo_config']
619 config = call_context['repo_config']
620
620
621 environ['PATH_INFO'] = call_context['path_info']
621 environ['PATH_INFO'] = call_context['path_info']
622 self.set_env_from_config(environ, config)
622 self.set_env_from_config(environ, config)
623
623
624 content_type = environ.get('CONTENT_TYPE', '')
624 content_type = environ.get('CONTENT_TYPE', '')
625
625
626 path = environ['PATH_INFO']
626 path = environ['PATH_INFO']
627 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
627 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
628 log.debug(
628 log.debug(
629 'LFS: Detecting if request `%s` is LFS server path based '
629 'LFS: Detecting if request `%s` is LFS server path based '
630 'on content type:`%s`, is_lfs:%s',
630 'on content type:`%s`, is_lfs:%s',
631 path, content_type, is_lfs_request)
631 path, content_type, is_lfs_request)
632
632
633 if not is_lfs_request:
633 if not is_lfs_request:
634 # fallback detection by path
634 # fallback detection by path
635 if GIT_LFS_PROTO_PAT.match(path):
635 if GIT_LFS_PROTO_PAT.match(path):
636 is_lfs_request = True
636 is_lfs_request = True
637 log.debug(
637 log.debug(
638 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
638 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
639 path, is_lfs_request)
639 path, is_lfs_request)
640
640
641 if is_lfs_request:
641 if is_lfs_request:
642 app = scm_app.create_git_lfs_wsgi_app(
642 app = scm_app.create_git_lfs_wsgi_app(
643 repo_path, repo_name, config)
643 repo_path, repo_name, config)
644 else:
644 else:
645 app = scm_app.create_git_wsgi_app(
645 app = scm_app.create_git_wsgi_app(
646 repo_path, repo_name, config)
646 repo_path, repo_name, config)
647
647
648 log.debug('http-app: starting app handler '
648 log.debug('http-app: starting app handler '
649 'with %s and process request', app)
649 'with %s and process request', app)
650
650
651 return app(environ, start_response)
651 return app(environ, start_response)
652
652
653 return _git_stream
653 return _git_stream
654
654
655 def handle_vcs_exception(self, exception, request):
655 def handle_vcs_exception(self, exception, request):
656 _vcs_kind = getattr(exception, '_vcs_kind', '')
656 _vcs_kind = getattr(exception, '_vcs_kind', '')
657
657
658 if _vcs_kind == 'repo_locked':
658 if _vcs_kind == 'repo_locked':
659 headers_call_context = get_headers_call_context(request.environ)
659 headers_call_context = get_headers_call_context(request.environ)
660 status_code = safe_int(headers_call_context['locked_status_code'])
660 status_code = safe_int(headers_call_context['locked_status_code'])
661
661
662 return HTTPRepoLocked(
662 return HTTPRepoLocked(
663 title=str(exception), status_code=status_code, headers=[('X-Rc-Locked', '1')])
663 title=str(exception), status_code=status_code, headers=[('X-Rc-Locked', '1')])
664
664
665 elif _vcs_kind == 'repo_branch_protected':
665 elif _vcs_kind == 'repo_branch_protected':
666 # Get custom repo-branch-protected status code if present.
666 # Get custom repo-branch-protected status code if present.
667 return HTTPRepoBranchProtected(
667 return HTTPRepoBranchProtected(
668 title=str(exception), headers=[('X-Rc-Branch-Protection', '1')])
668 title=str(exception), headers=[('X-Rc-Branch-Protection', '1')])
669
669
670 exc_info = request.exc_info
670 exc_info = request.exc_info
671 store_exception(id(exc_info), exc_info)
671 store_exception(id(exc_info), exc_info)
672
672
673 traceback_info = 'unavailable'
673 traceback_info = 'unavailable'
674 if request.exc_info:
674 if request.exc_info:
675 traceback_info = format_exc(request.exc_info)
675 traceback_info = format_exc(request.exc_info)
676
676
677 log.error(
677 log.error(
678 'error occurred handling this request for path: %s, \n%s',
678 'error occurred handling this request for path: %s, \n%s',
679 request.path, traceback_info)
679 request.path, traceback_info)
680
680
681 statsd = request.registry.statsd
681 statsd = request.registry.statsd
682 if statsd:
682 if statsd:
683 exc_type = f"{exception.__class__.__module__}.{exception.__class__.__name__}"
683 exc_type = f"{exception.__class__.__module__}.{exception.__class__.__name__}"
684 statsd.incr('vcsserver_exception_total',
684 statsd.incr('vcsserver_exception_total',
685 tags=[f"type:{exc_type}"])
685 tags=[f"type:{exc_type}"])
686 raise exception
686 raise exception
687
687
688
688
689 class ResponseFilter:
689 class ResponseFilter:
690
690
691 def __init__(self, start_response):
691 def __init__(self, start_response):
692 self._start_response = start_response
692 self._start_response = start_response
693
693
694 def __call__(self, status, response_headers, exc_info=None):
694 def __call__(self, status, response_headers, exc_info=None):
695 headers = tuple(
695 headers = tuple(
696 (h, v) for h, v in response_headers
696 (h, v) for h, v in response_headers
697 if not wsgiref.util.is_hop_by_hop(h))
697 if not wsgiref.util.is_hop_by_hop(h))
698 return self._start_response(status, headers, exc_info)
698 return self._start_response(status, headers, exc_info)
699
699
700
700
701 def sanitize_settings_and_apply_defaults(global_config, settings):
701 def sanitize_settings_and_apply_defaults(global_config, settings):
702 _global_settings_maker = SettingsMaker(global_config)
702 _global_settings_maker = SettingsMaker(global_config)
703 settings_maker = SettingsMaker(settings)
703 settings_maker = SettingsMaker(settings)
704
704
705 settings_maker.make_setting('logging.autoconfigure', False, parser='bool')
705 settings_maker.make_setting('logging.autoconfigure', False, parser='bool')
706
706
707 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
707 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
708 settings_maker.enable_logging(logging_conf)
708 settings_maker.enable_logging(logging_conf)
709
709
710 # Default includes, possible to change as a user
710 # Default includes, possible to change as a user
711 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
711 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
712 log.debug("Using the following pyramid.includes: %s", pyramid_includes)
712 log.debug("Using the following pyramid.includes: %s", pyramid_includes)
713
713
714 settings_maker.make_setting('__file__', global_config.get('__file__'))
714 settings_maker.make_setting('__file__', global_config.get('__file__'))
715
715
716 settings_maker.make_setting('pyramid.default_locale_name', 'en')
716 settings_maker.make_setting('pyramid.default_locale_name', 'en')
717 settings_maker.make_setting('locale', 'en_US.UTF-8')
717 settings_maker.make_setting('locale', 'en_US.UTF-8')
718
718
719 settings_maker.make_setting('core.binary_dir', '/usr/local/bin/rhodecode_bin/vcs_bin', parser='string:noquote')
719 settings_maker.make_setting(
720 'core.binary_dir', '/usr/local/bin/rhodecode_bin/vcs_bin',
721 default_when_empty=True, parser='string:noquote')
720
722
721 temp_store = tempfile.gettempdir()
723 temp_store = tempfile.gettempdir()
722 default_cache_dir = os.path.join(temp_store, 'rc_cache')
724 default_cache_dir = os.path.join(temp_store, 'rc_cache')
723 # save default, cache dir, and use it for all backends later.
725 # save default, cache dir, and use it for all backends later.
724 default_cache_dir = settings_maker.make_setting(
726 default_cache_dir = settings_maker.make_setting(
725 'cache_dir',
727 'cache_dir',
726 default=default_cache_dir, default_when_empty=True,
728 default=default_cache_dir, default_when_empty=True,
727 parser='dir:ensured')
729 parser='dir:ensured')
728
730
729 # exception store cache
731 # exception store cache
730 settings_maker.make_setting(
732 settings_maker.make_setting(
731 'exception_tracker.store_path',
733 'exception_tracker.store_path',
732 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
734 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
733 parser='dir:ensured'
735 parser='dir:ensured'
734 )
736 )
735
737
736 # repo_object cache defaults
738 # repo_object cache defaults
737 settings_maker.make_setting(
739 settings_maker.make_setting(
738 'rc_cache.repo_object.backend',
740 'rc_cache.repo_object.backend',
739 default='dogpile.cache.rc.file_namespace',
741 default='dogpile.cache.rc.file_namespace',
740 parser='string')
742 parser='string')
741 settings_maker.make_setting(
743 settings_maker.make_setting(
742 'rc_cache.repo_object.expiration_time',
744 'rc_cache.repo_object.expiration_time',
743 default=30 * 24 * 60 * 60, # 30days
745 default=30 * 24 * 60 * 60, # 30days
744 parser='int')
746 parser='int')
745 settings_maker.make_setting(
747 settings_maker.make_setting(
746 'rc_cache.repo_object.arguments.filename',
748 'rc_cache.repo_object.arguments.filename',
747 default=os.path.join(default_cache_dir, 'vcsserver_cache_repo_object.db'),
749 default=os.path.join(default_cache_dir, 'vcsserver_cache_repo_object.db'),
748 parser='string')
750 parser='string')
749
751
750 # statsd
752 # statsd
751 settings_maker.make_setting('statsd.enabled', False, parser='bool')
753 settings_maker.make_setting('statsd.enabled', False, parser='bool')
752 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
754 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
753 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
755 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
754 settings_maker.make_setting('statsd.statsd_prefix', '')
756 settings_maker.make_setting('statsd.statsd_prefix', '')
755 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
757 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
756
758
757 settings_maker.env_expand()
759 settings_maker.env_expand()
758
760
759
761
760 def main(global_config, **settings):
762 def main(global_config, **settings):
761 start_time = time.time()
763 start_time = time.time()
762 log.info('Pyramid app config starting')
764 log.info('Pyramid app config starting')
763
765
764 if MercurialFactory:
766 if MercurialFactory:
765 hgpatches.patch_largefiles_capabilities()
767 hgpatches.patch_largefiles_capabilities()
766 hgpatches.patch_subrepo_type_mapping()
768 hgpatches.patch_subrepo_type_mapping()
767
769
768 # Fill in and sanitize the defaults & do ENV expansion
770 # Fill in and sanitize the defaults & do ENV expansion
769 sanitize_settings_and_apply_defaults(global_config, settings)
771 sanitize_settings_and_apply_defaults(global_config, settings)
770
772
771 # init and bootstrap StatsdClient
773 # init and bootstrap StatsdClient
772 StatsdClient.setup(settings)
774 StatsdClient.setup(settings)
773
775
774 pyramid_app = HTTPApplication(settings=settings, global_config=global_config).wsgi_app()
776 pyramid_app = HTTPApplication(settings=settings, global_config=global_config).wsgi_app()
775 total_time = time.time() - start_time
777 total_time = time.time() - start_time
776 log.info('Pyramid app created and configured in %.2fs', total_time)
778 log.info('Pyramid app created and configured in %.2fs', total_time)
777 return pyramid_app
779 return pyramid_app
General Comments 0
You need to be logged in to leave comments. Login now