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