##// END OF EJS Templates
env: made configuration completly overridable by env variables
super-admin -
r1023:28e26155 default
parent child Browse files
Show More
@@ -1,271 +1,276 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 ; #################################
4 4 ; RHODECODE VCSSERVER CONFIGURATION
5 5 ; #################################
6 6
7 7 [server:main]
8 8 ; COMMON HOST/IP CONFIG
9 9 host = 0.0.0.0
10 10 port = 9900
11 11
12 12 ; ##################################################
13 13 ; WAITRESS WSGI SERVER - Recommended for Development
14 14 ; ##################################################
15 15
16 16 ; use server type
17 17 use = egg:waitress#main
18 18
19 19 ; number of worker threads
20 20 threads = 5
21 21
22 22 ; MAX BODY SIZE 100GB
23 23 max_request_body_size = 107374182400
24 24
25 25 ; Use poll instead of select, fixes file descriptors limits problems.
26 26 ; May not work on old windows systems.
27 27 asyncore_use_poll = true
28 28
29 29
30 30 ; ###########################
31 31 ; GUNICORN APPLICATION SERVER
32 32 ; ###########################
33 33
34 34 ; run with gunicorn --paste rhodecode.ini
35 35
36 36 ; Module to use, this setting shouldn't be changed
37 37 #use = egg:gunicorn#main
38 38
39 39 ; Sets the number of process workers. More workers means more concurrent connections
40 40 ; RhodeCode can handle at the same time. Each additional worker also it increases
41 41 ; memory usage as each has it's own set of caches.
42 42 ; Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
43 43 ; than 8-10 unless for really big deployments .e.g 700-1000 users.
44 44 ; `instance_id = *` must be set in the [app:main] section below (which is the default)
45 45 ; when using more than 1 worker.
46 46 #workers = 2
47 47
48 48 ; Gunicorn access log level
49 49 #loglevel = info
50 50
51 51 ; Process name visible in process list
52 52 #proc_name = rhodecode_vcsserver
53 53
54 54 ; Type of worker class, one of `sync`, `gevent`
55 55 ; currently `sync` is the only option allowed.
56 56 #worker_class = sync
57 57
58 58 ; The maximum number of simultaneous clients. Valid only for gevent
59 59 #worker_connections = 10
60 60
61 61 ; Max number of requests that worker will handle before being gracefully restarted.
62 62 ; Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
63 63 #max_requests = 1000
64 64 #max_requests_jitter = 30
65 65
66 66 ; Amount of time a worker can spend with handling a request before it
67 67 ; gets killed and restarted. By default set to 21600 (6hrs)
68 68 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
69 69 #timeout = 21600
70 70
71 71 ; The maximum size of HTTP request line in bytes.
72 72 ; 0 for unlimited
73 73 #limit_request_line = 0
74 74
75 75 ; Limit the number of HTTP headers fields in a request.
76 76 ; By default this value is 100 and can't be larger than 32768.
77 77 #limit_request_fields = 32768
78 78
79 79 ; Limit the allowed size of an HTTP request header field.
80 80 ; Value is a positive number or 0.
81 81 ; Setting it to 0 will allow unlimited header field sizes.
82 82 #limit_request_field_size = 0
83 83
84 84 ; Timeout for graceful workers restart.
85 85 ; After receiving a restart signal, workers have this much time to finish
86 86 ; serving requests. Workers still alive after the timeout (starting from the
87 87 ; receipt of the restart signal) are force killed.
88 88 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
89 89 #graceful_timeout = 21600
90 90
91 91 # The number of seconds to wait for requests on a Keep-Alive connection.
92 92 # Generally set in the 1-5 seconds range.
93 93 #keepalive = 2
94 94
95 95 ; Maximum memory usage that each worker can use before it will receive a
96 96 ; graceful restart signal 0 = memory monitoring is disabled
97 97 ; Examples: 268435456 (256MB), 536870912 (512MB)
98 98 ; 1073741824 (1GB), 2147483648 (2GB), 4294967296 (4GB)
99 99 #memory_max_usage = 0
100 100
101 101 ; How often in seconds to check for memory usage for each gunicorn worker
102 102 #memory_usage_check_interval = 60
103 103
104 104 ; Threshold value for which we don't recycle worker if GarbageCollection
105 105 ; frees up enough resources. Before each restart we try to run GC on worker
106 106 ; in case we get enough free memory after that, restart will not happen.
107 107 #memory_usage_recovery_threshold = 0.8
108 108
109 109
110 110 [app:main]
111 111 ; The %(here)s variable will be replaced with the absolute path of parent directory
112 112 ; of this file
113 113 ; Each option in the app:main can be override by an environmental variable
114 114 ;
115 115 ;To override an option:
116 116 ;
117 117 ;RC_<KeyName>
118 118 ;Everything should be uppercase, . and - should be replaced by _.
119 119 ;For example, if you have these configuration settings:
120 120 ;rc_cache.repo_object.backend = foo
121 121 ;can be overridden by
122 122 ;export RC_CACHE_REPO_OBJECT_BACKEND=foo
123 123
124 124 use = egg:rhodecode-vcsserver
125 125
126 126
127 127 ; #############
128 128 ; DEBUG OPTIONS
129 129 ; #############
130 130
131 131 # During development the we want to have the debug toolbar enabled
132 132 pyramid.includes =
133 133 pyramid_debugtoolbar
134 134
135 135 debugtoolbar.hosts = 0.0.0.0/0
136 136 debugtoolbar.exclude_prefixes =
137 137 /css
138 138 /fonts
139 139 /images
140 140 /js
141 141
142 142 ; #################
143 143 ; END DEBUG OPTIONS
144 144 ; #################
145 145
146 146 ; Pyramid default locales, we need this to be set
147 147 #pyramid.default_locale_name = en
148 148
149 149 ; default locale used by VCS systems
150 150 #locale = en_US.UTF-8
151 151
152 152 ; path to binaries for vcsserver, it should be set by the installer
153 153 ; at installation time, e.g /home/user/.rccontrol/vcsserver-1/profile/bin
154 154 ; it can also be a path to nix-build output in case of development
155 155 core.binary_dir = ""
156 156
157 157 ; Custom exception store path, defaults to TMPDIR
158 158 ; This is used to store exception from RhodeCode in shared directory
159 159 #exception_tracker.store_path =
160 160
161 161 ; #############
162 162 ; DOGPILE CACHE
163 163 ; #############
164 164
165 165 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
166 166 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
167 167 #cache_dir = %(here)s/data
168 168
169 169 ; ***************************************
170 170 ; `repo_object` cache, default file based
171 171 ; ***************************************
172 172
173 173 ; `repo_object` cache settings for vcs methods for repositories
174 174 #rc_cache.repo_object.backend = dogpile.cache.rc.file_namespace
175 175
176 176 ; cache auto-expires after N seconds
177 177 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
178 178 #rc_cache.repo_object.expiration_time = 2592000
179 179
180 180 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
181 181 #rc_cache.repo_object.arguments.filename = /tmp/vcsserver_cache_repo_object.db
182 182
183 183 ; ***********************************************************
184 184 ; `repo_object` cache with redis backend
185 185 ; recommended for larger instance, and for better performance
186 186 ; ***********************************************************
187 187
188 188 ; `repo_object` cache settings for vcs methods for repositories
189 189 #rc_cache.repo_object.backend = dogpile.cache.rc.redis_msgpack
190 190
191 191 ; cache auto-expires after N seconds
192 192 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
193 193 #rc_cache.repo_object.expiration_time = 2592000
194 194
195 195 ; redis_expiration_time needs to be greater then expiration_time
196 196 #rc_cache.repo_object.arguments.redis_expiration_time = 3592000
197 197
198 198 #rc_cache.repo_object.arguments.host = localhost
199 199 #rc_cache.repo_object.arguments.port = 6379
200 200 #rc_cache.repo_object.arguments.db = 5
201 201 #rc_cache.repo_object.arguments.socket_timeout = 30
202 202 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
203 203 #rc_cache.repo_object.arguments.distributed_lock = true
204 204
205 205 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
206 206 #rc_cache.repo_object.arguments.lock_auto_renewal = true
207 207
208 208 ; Statsd client config, this is used to send metrics to statsd
209 209 ; We recommend setting statsd_exported and scrape them using Promethues
210 210 #statsd.enabled = false
211 211 #statsd.statsd_host = 0.0.0.0
212 212 #statsd.statsd_port = 8125
213 213 #statsd.statsd_prefix =
214 214 #statsd.statsd_ipv6 = false
215 215
216 216 ; configure logging automatically at server startup set to false
217 217 ; to use the below custom logging config.
218 ; RC_LOGGING_FORMATTER
219 ; RC_LOGGING_LEVEL
220 ; env variables can control the settings for logging in case of autoconfigure
221
218 222 #logging.autoconfigure = true
219 223
220 224 ; specify your own custom logging config file to configure logging
221 225 #logging.logging_conf_file = /path/to/custom_logging.ini
222 226
223 227 ; #####################
224 228 ; LOGGING CONFIGURATION
225 229 ; #####################
230
226 231 #[loggers]
227 232 #keys = root, vcsserver
228 233
229 234 #[handlers]
230 235 #keys = console
231 236
232 237 #[formatters]
233 238 #keys = generic
234 239
235 240 ; #######
236 241 ; LOGGERS
237 242 ; #######
238 243 #[logger_root]
239 244 #level = NOTSET
240 245 #handlers = console
241 246
242 247 #[logger_vcsserver]
243 248 #level = INFO
244 249 #handlers =
245 250 #qualname = vcsserver
246 251 #propagate = 1
247 252
248 253 ; ########
249 254 ; HANDLERS
250 255 ; ########
251 256
252 257 #[handler_console]
253 258 #class = StreamHandler
254 259 #args = (sys.stderr, )
255 260 #level = INFO
256 261 ; To enable JSON formatted logs replace generic with json
257 262 ; This allows sending properly formatted logs to grafana loki or elasticsearch
258 263 #formatter = json
259 264 #formatter = generic
260 265
261 266 ; ##########
262 267 ; FORMATTERS
263 268 ; ##########
264 269
265 270 #[formatter_generic]
266 271 #format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
267 272 #datefmt = %Y-%m-%d %H:%M:%S
268 273
269 274 #[formatter_json]
270 275 #format = %(timestamp)s %(levelname)s %(name)s %(message)s %(req_id)s
271 276 #class = vcsserver.lib._vendor.jsonlogger.JsonFormatter
@@ -1,234 +1,239 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 ; #################################
4 4 ; RHODECODE VCSSERVER CONFIGURATION
5 5 ; #################################
6 6
7 7 [server:main]
8 8 ; COMMON HOST/IP CONFIG
9 9 host = 127.0.0.1
10 10 port = 9900
11 11
12 12
13 13 ; ###########################
14 14 ; GUNICORN APPLICATION SERVER
15 15 ; ###########################
16 16
17 17 ; run with gunicorn --paste rhodecode.ini
18 18
19 19 ; Module to use, this setting shouldn't be changed
20 20 use = egg:gunicorn#main
21 21
22 22 ; Sets the number of process workers. More workers means more concurrent connections
23 23 ; RhodeCode can handle at the same time. Each additional worker also it increases
24 24 ; memory usage as each has it's own set of caches.
25 25 ; Recommended value is (2 * NUMBER_OF_CPUS + 1), eg 2CPU = 5 workers, but no more
26 26 ; than 8-10 unless for really big deployments .e.g 700-1000 users.
27 27 ; `instance_id = *` must be set in the [app:main] section below (which is the default)
28 28 ; when using more than 1 worker.
29 29 workers = 2
30 30
31 31 ; Gunicorn access log level
32 32 loglevel = info
33 33
34 34 ; Process name visible in process list
35 35 proc_name = rhodecode_vcsserver
36 36
37 37 ; Type of worker class, one of `sync`, `gevent`
38 38 ; currently `sync` is the only option allowed.
39 39 worker_class = sync
40 40
41 41 ; The maximum number of simultaneous clients. Valid only for gevent
42 42 worker_connections = 10
43 43
44 44 ; Max number of requests that worker will handle before being gracefully restarted.
45 45 ; Prevents memory leaks, jitter adds variability so not all workers are restarted at once.
46 46 max_requests = 1000
47 47 max_requests_jitter = 30
48 48
49 49 ; Amount of time a worker can spend with handling a request before it
50 50 ; gets killed and restarted. By default set to 21600 (6hrs)
51 51 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
52 52 timeout = 21600
53 53
54 54 ; The maximum size of HTTP request line in bytes.
55 55 ; 0 for unlimited
56 56 limit_request_line = 0
57 57
58 58 ; Limit the number of HTTP headers fields in a request.
59 59 ; By default this value is 100 and can't be larger than 32768.
60 60 limit_request_fields = 32768
61 61
62 62 ; Limit the allowed size of an HTTP request header field.
63 63 ; Value is a positive number or 0.
64 64 ; Setting it to 0 will allow unlimited header field sizes.
65 65 limit_request_field_size = 0
66 66
67 67 ; Timeout for graceful workers restart.
68 68 ; After receiving a restart signal, workers have this much time to finish
69 69 ; serving requests. Workers still alive after the timeout (starting from the
70 70 ; receipt of the restart signal) are force killed.
71 71 ; Examples: 1800 (30min), 3600 (1hr), 7200 (2hr), 43200 (12h)
72 72 graceful_timeout = 21600
73 73
74 74 # The number of seconds to wait for requests on a Keep-Alive connection.
75 75 # Generally set in the 1-5 seconds range.
76 76 keepalive = 2
77 77
78 78 ; Maximum memory usage that each worker can use before it will receive a
79 79 ; graceful restart signal 0 = memory monitoring is disabled
80 80 ; Examples: 268435456 (256MB), 536870912 (512MB)
81 81 ; 1073741824 (1GB), 2147483648 (2GB), 4294967296 (4GB)
82 82 memory_max_usage = 0
83 83
84 84 ; How often in seconds to check for memory usage for each gunicorn worker
85 85 memory_usage_check_interval = 60
86 86
87 87 ; Threshold value for which we don't recycle worker if GarbageCollection
88 88 ; frees up enough resources. Before each restart we try to run GC on worker
89 89 ; in case we get enough free memory after that, restart will not happen.
90 90 memory_usage_recovery_threshold = 0.8
91 91
92 92
93 93 [app:main]
94 94 ; The %(here)s variable will be replaced with the absolute path of parent directory
95 95 ; of this file
96 96 ; Each option in the app:main can be override by an environmental variable
97 97 ;
98 98 ;To override an option:
99 99 ;
100 100 ;RC_<KeyName>
101 101 ;Everything should be uppercase, . and - should be replaced by _.
102 102 ;For example, if you have these configuration settings:
103 103 ;rc_cache.repo_object.backend = foo
104 104 ;can be overridden by
105 105 ;export RC_CACHE_REPO_OBJECT_BACKEND=foo
106 106
107 107 use = egg:rhodecode-vcsserver
108 108
109 109 ; Pyramid default locales, we need this to be set
110 110 #pyramid.default_locale_name = en
111 111
112 112 ; default locale used by VCS systems
113 113 #locale = en_US.UTF-8
114 114
115 115 ; path to binaries for vcsserver, it should be set by the installer
116 116 ; at installation time, e.g /home/user/.rccontrol/vcsserver-1/profile/bin
117 117 ; it can also be a path to nix-build output in case of development
118 118 core.binary_dir = ""
119 119
120 120 ; Custom exception store path, defaults to TMPDIR
121 121 ; This is used to store exception from RhodeCode in shared directory
122 122 #exception_tracker.store_path =
123 123
124 124 ; #############
125 125 ; DOGPILE CACHE
126 126 ; #############
127 127
128 128 ; Default cache dir for caches. Putting this into a ramdisk can boost performance.
129 129 ; eg. /tmpfs/data_ramdisk, however this directory might require large amount of space
130 130 #cache_dir = %(here)s/data
131 131
132 132 ; ***************************************
133 133 ; `repo_object` cache, default file based
134 134 ; ***************************************
135 135
136 136 ; `repo_object` cache settings for vcs methods for repositories
137 137 #rc_cache.repo_object.backend = dogpile.cache.rc.file_namespace
138 138
139 139 ; cache auto-expires after N seconds
140 140 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
141 141 #rc_cache.repo_object.expiration_time = 2592000
142 142
143 143 ; file cache store path. Defaults to `cache_dir =` value or tempdir if both values are not set
144 144 #rc_cache.repo_object.arguments.filename = /tmp/vcsserver_cache_repo_object.db
145 145
146 146 ; ***********************************************************
147 147 ; `repo_object` cache with redis backend
148 148 ; recommended for larger instance, and for better performance
149 149 ; ***********************************************************
150 150
151 151 ; `repo_object` cache settings for vcs methods for repositories
152 152 #rc_cache.repo_object.backend = dogpile.cache.rc.redis_msgpack
153 153
154 154 ; cache auto-expires after N seconds
155 155 ; Examples: 86400 (1Day), 604800 (7Days), 1209600 (14Days), 2592000 (30days), 7776000 (90Days)
156 156 #rc_cache.repo_object.expiration_time = 2592000
157 157
158 158 ; redis_expiration_time needs to be greater then expiration_time
159 159 #rc_cache.repo_object.arguments.redis_expiration_time = 3592000
160 160
161 161 #rc_cache.repo_object.arguments.host = localhost
162 162 #rc_cache.repo_object.arguments.port = 6379
163 163 #rc_cache.repo_object.arguments.db = 5
164 164 #rc_cache.repo_object.arguments.socket_timeout = 30
165 165 ; more Redis options: https://dogpilecache.sqlalchemy.org/en/latest/api.html#redis-backends
166 166 #rc_cache.repo_object.arguments.distributed_lock = true
167 167
168 168 ; auto-renew lock to prevent stale locks, slower but safer. Use only if problems happen
169 169 #rc_cache.repo_object.arguments.lock_auto_renewal = true
170 170
171 171 ; Statsd client config, this is used to send metrics to statsd
172 172 ; We recommend setting statsd_exported and scrape them using Promethues
173 173 #statsd.enabled = false
174 174 #statsd.statsd_host = 0.0.0.0
175 175 #statsd.statsd_port = 8125
176 176 #statsd.statsd_prefix =
177 177 #statsd.statsd_ipv6 = false
178 178
179 179 ; configure logging automatically at server startup set to false
180 180 ; to use the below custom logging config.
181 ; RC_LOGGING_FORMATTER
182 ; RC_LOGGING_LEVEL
183 ; env variables can control the settings for logging in case of autoconfigure
184
181 185 #logging.autoconfigure = true
182 186
183 187 ; specify your own custom logging config file to configure logging
184 188 #logging.logging_conf_file = /path/to/custom_logging.ini
185 189
186 190 ; #####################
187 191 ; LOGGING CONFIGURATION
188 192 ; #####################
193
189 194 #[loggers]
190 195 #keys = root, vcsserver
191 196
192 197 #[handlers]
193 198 #keys = console
194 199
195 200 #[formatters]
196 201 #keys = generic
197 202
198 203 ; #######
199 204 ; LOGGERS
200 205 ; #######
201 206 #[logger_root]
202 207 #level = NOTSET
203 208 #handlers = console
204 209
205 210 #[logger_vcsserver]
206 211 #level = INFO
207 212 #handlers =
208 213 #qualname = vcsserver
209 214 #propagate = 1
210 215
211 216 ; ########
212 217 ; HANDLERS
213 218 ; ########
214 219
215 220 #[handler_console]
216 221 #class = StreamHandler
217 222 #args = (sys.stderr, )
218 223 #level = INFO
219 224 ; To enable JSON formatted logs replace generic with json
220 225 ; This allows sending properly formatted logs to grafana loki or elasticsearch
221 226 #formatter = json
222 227 #formatter = generic
223 228
224 229 ; ##########
225 230 ; FORMATTERS
226 231 ; ##########
227 232
228 233 #[formatter_generic]
229 234 #format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
230 235 #datefmt = %Y-%m-%d %H:%M:%S
231 236
232 237 #[formatter_json]
233 238 #format = %(timestamp)s %(levelname)s %(name)s %(message)s %(req_id)s
234 239 #class = vcsserver.lib._vendor.jsonlogger.JsonFormatter
@@ -1,190 +1,207 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import os
22 22 import textwrap
23 23 import string
24 24 import functools
25 25 import logging
26 26 import tempfile
27 27 import logging.config
28 28 log = logging.getLogger(__name__)
29 29
30 # skip keys, that are set here, so we don't double process those
31 set_keys = {
32 '__file__': ''
33 }
34
30 35
31 36 def str2bool(_str):
32 37 """
33 38 returns True/False value from given string, it tries to translate the
34 39 string into boolean
35 40
36 41 :param _str: string value to translate into boolean
37 42 :rtype: boolean
38 43 :returns: boolean from given string
39 44 """
40 45 if _str is None:
41 46 return False
42 47 if _str in (True, False):
43 48 return _str
44 49 _str = str(_str).strip().lower()
45 50 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
46 51
47 52
48 53 def aslist(obj, sep=None, strip=True):
49 54 """
50 55 Returns given string separated by sep as list
51 56
52 57 :param obj:
53 58 :param sep:
54 59 :param strip:
55 60 """
56 61 if isinstance(obj, (basestring,)):
57 62 if obj in ['', ""]:
58 63 return []
59 64
60 65 lst = obj.split(sep)
61 66 if strip:
62 67 lst = [v.strip() for v in lst]
63 68 return lst
64 69 elif isinstance(obj, (list, tuple)):
65 70 return obj
66 71 elif obj is None:
67 72 return []
68 73 else:
69 74 return [obj]
70 75
71 76
72 77 class SettingsMaker(object):
73 78
74 79 def __init__(self, app_settings):
75 80 self.settings = app_settings
76 81
77 82 @classmethod
78 83 def _bool_func(cls, input_val):
79 84 if isinstance(input_val, unicode):
80 85 input_val = input_val.encode('utf8')
81 86 return str2bool(input_val)
82 87
83 88 @classmethod
84 89 def _int_func(cls, input_val):
85 90 return int(input_val)
86 91
87 92 @classmethod
88 93 def _list_func(cls, input_val, sep=','):
89 94 return aslist(input_val, sep=sep)
90 95
91 96 @classmethod
92 97 def _string_func(cls, input_val, lower=True):
93 98 if lower:
94 99 input_val = input_val.lower()
95 100 return input_val
96 101
97 102 @classmethod
98 103 def _float_func(cls, input_val):
99 104 return float(input_val)
100 105
101 106 @classmethod
102 107 def _dir_func(cls, input_val, ensure_dir=False, mode=0o755):
103 108
104 109 # ensure we have our dir created
105 110 if not os.path.isdir(input_val) and ensure_dir:
106 111 os.makedirs(input_val, mode=mode)
107 112
108 113 if not os.path.isdir(input_val):
109 114 raise Exception('Dir at {} does not exist'.format(input_val))
110 115 return input_val
111 116
112 117 @classmethod
113 118 def _file_path_func(cls, input_val, ensure_dir=False, mode=0o755):
114 119 dirname = os.path.dirname(input_val)
115 120 cls._dir_func(dirname, ensure_dir=ensure_dir)
116 121 return input_val
117 122
118 123 @classmethod
119 124 def _key_transformator(cls, key):
120 125 return "{}_{}".format('RC'.upper(), key.upper().replace('.', '_').replace('-', '_'))
121 126
127 def maybe_env_key(self, key):
128 # now maybe we have this KEY in env, search and use the value with higher priority.
129 transformed_key = self._key_transformator(key)
130 envvar_value = os.environ.get(transformed_key)
131 if envvar_value:
132 log.debug('using `%s` key instead of `%s` key for config', transformed_key, key)
133
134 return envvar_value
135
136 def env_expand(self):
137 replaced = {}
138 for k, v in self.settings.items():
139 if k not in set_keys:
140 envvar_value = self.maybe_env_key(k)
141 if envvar_value:
142 replaced[k] = envvar_value
143 set_keys[k] = envvar_value
144
145 # replace ALL keys updated
146 self.settings.update(replaced)
147
122 148 def enable_logging(self, logging_conf=None, level='INFO', formatter='generic'):
123 149 """
124 150 Helper to enable debug on running instance
125 151 :return:
126 152 """
127 153
128 154 if not str2bool(self.settings.get('logging.autoconfigure')):
129 155 log.info('logging configuration based on main .ini file')
130 156 return
131 157
132 158 if logging_conf is None:
133 159 logging_conf = self.settings.get('logging.logging_conf_file') or ''
134 160
135 161 if not os.path.isfile(logging_conf):
136 log.error('Unable to setup logging based on %s, file does not exist...', logging_conf)
162 log.error('Unable to setup logging based on %s, '
163 'file does not exist.... specify path using logging.logging_conf_file= config setting. ', logging_conf)
137 164 return
138 165
139 166 with open(logging_conf, 'rb') as f:
140 167 ini_template = textwrap.dedent(f.read())
141 168 ini_template = string.Template(ini_template).safe_substitute(
142 169 RC_LOGGING_LEVEL=os.environ.get('RC_LOGGING_LEVEL', '') or level,
143 170 RC_LOGGING_FORMATTER=os.environ.get('RC_LOGGING_FORMATTER', '') or formatter
144 171 )
145 172
146
147 with open(logging_conf, 'rb') as f:
148 ini_template = textwrap.dedent(f.read())
149 ini_template = string.Template(ini_template).safe_substitute(
150 RC_LOGGING_LEVEL=os.environ.get('RC_LOGGING_LEVEL', '') or 'INFO',
151 RC_LOGGING_FORMATTER=os.environ.get('RC_LOGGING_FORMATTER', '') or 'generic'
152 )
153
154 173 with tempfile.NamedTemporaryFile(prefix='rc_logging_', suffix='.ini', delete=False) as f:
155 174 log.info('Saved Temporary LOGGING config at %s', f.name)
156 175 f.write(ini_template)
157 176
158 177 logging.config.fileConfig(f.name)
159 178 os.remove(f.name)
160 179
161 180 def make_setting(self, key, default, lower=False, default_when_empty=False, parser=None):
162
163 181 input_val = self.settings.get(key, default)
164 182
165 183 if default_when_empty and not input_val:
166 184 # use default value when value is set in the config but it is empty
167 185 input_val = default
168 186
169 187 parser_func = {
170 188 'bool': self._bool_func,
171 189 'int': self._int_func,
172 190 'list': self._list_func,
173 191 'list:newline': functools.partial(self._list_func, sep='/n'),
174 192 'list:spacesep': functools.partial(self._list_func, sep=' '),
175 193 'string': functools.partial(self._string_func, lower=lower),
176 194 'dir': self._dir_func,
177 195 'dir:ensured': functools.partial(self._dir_func, ensure_dir=True),
178 196 'file': self._file_path_func,
179 197 'file:ensured': functools.partial(self._file_path_func, ensure_dir=True),
180 198 None: lambda i: i
181 199 }[parser]
182 200
183 # now maybe we have this KEY in env, search and use the value with higher priority.
184 transformed_key = self._key_transformator(key)
185 envvar_value = os.environ.get(transformed_key)
201 envvar_value = self.maybe_env_key(key)
186 202 if envvar_value:
187 log.debug('using `%s` key instead of `%s` key for config', transformed_key, key)
188 203 input_val = envvar_value
204 set_keys[key] = input_val
205
189 206 self.settings[key] = parser_func(input_val)
190 207 return self.settings[key]
@@ -1,750 +1,723 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-2020 RhodeCode GmbH
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import os
19 19 import sys
20 20 import base64
21 21 import locale
22 22 import logging
23 23 import uuid
24 24 import time
25 25 import wsgiref.util
26 26 import traceback
27 27 import tempfile
28 28 import psutil
29 29
30 30 from itertools import chain
31 31 from cStringIO import StringIO
32 32
33 33 import simplejson as json
34 34 import msgpack
35 35 from pyramid.config import Configurator
36 36 from pyramid.wsgi import wsgiapp
37 37 from pyramid.compat import configparser
38 38 from pyramid.response import Response
39 39 from vcsserver.config.settings_maker import SettingsMaker
40 40 from vcsserver.utils import safe_int
41 41 from vcsserver.lib.statsd_client import StatsdClient
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45 # due to Mercurial/glibc2.27 problems we need to detect if locale settings are
46 46 # causing problems and "fix" it in case they do and fallback to LC_ALL = C
47 47
48 48 try:
49 49 locale.setlocale(locale.LC_ALL, '')
50 50 except locale.Error as e:
51 51 log.error(
52 52 'LOCALE ERROR: failed to set LC_ALL, fallback to LC_ALL=C, org error: %s', e)
53 53 os.environ['LC_ALL'] = 'C'
54 54
55 55
56 56 import vcsserver
57 57 from vcsserver import remote_wsgi, scm_app, settings, hgpatches
58 58 from vcsserver.git_lfs.app import GIT_LFS_CONTENT_TYPE, GIT_LFS_PROTO_PAT
59 59 from vcsserver.echo_stub import remote_wsgi as remote_wsgi_stub
60 60 from vcsserver.echo_stub.echo_app import EchoApp
61 61 from vcsserver.exceptions import HTTPRepoLocked, HTTPRepoBranchProtected
62 62 from vcsserver.lib.exc_tracking import store_exception
63 63 from vcsserver.server import VcsServer
64 64
65 65 try:
66 66 from vcsserver.git import GitFactory, GitRemote
67 67 except ImportError:
68 68 GitFactory = None
69 69 GitRemote = None
70 70
71 71 try:
72 72 from vcsserver.hg import MercurialFactory, HgRemote
73 73 except ImportError:
74 74 MercurialFactory = None
75 75 HgRemote = None
76 76
77 77 try:
78 78 from vcsserver.svn import SubversionFactory, SvnRemote
79 79 except ImportError:
80 80 SubversionFactory = None
81 81 SvnRemote = None
82 82
83 83
84 84 def _is_request_chunked(environ):
85 85 stream = environ.get('HTTP_TRANSFER_ENCODING', '') == 'chunked'
86 86 return stream
87 87
88 88
89 89 def log_max_fd():
90 90 try:
91 91 maxfd = psutil.Process().rlimit(psutil.RLIMIT_NOFILE)[1]
92 92 log.info('Max file descriptors value: %s', maxfd)
93 93 except Exception:
94 94 pass
95 95
96 96
97 97 class VCS(object):
98 98 def __init__(self, locale_conf=None, cache_config=None):
99 99 self.locale = locale_conf
100 100 self.cache_config = cache_config
101 101 self._configure_locale()
102 102
103 103 log_max_fd()
104 104
105 105 if GitFactory and GitRemote:
106 106 git_factory = GitFactory()
107 107 self._git_remote = GitRemote(git_factory)
108 108 else:
109 109 log.info("Git client import failed")
110 110
111 111 if MercurialFactory and HgRemote:
112 112 hg_factory = MercurialFactory()
113 113 self._hg_remote = HgRemote(hg_factory)
114 114 else:
115 115 log.info("Mercurial client import failed")
116 116
117 117 if SubversionFactory and SvnRemote:
118 118 svn_factory = SubversionFactory()
119 119
120 120 # hg factory is used for svn url validation
121 121 hg_factory = MercurialFactory()
122 122 self._svn_remote = SvnRemote(svn_factory, hg_factory=hg_factory)
123 123 else:
124 124 log.warning("Subversion client import failed")
125 125
126 126 self._vcsserver = VcsServer()
127 127
128 128 def _configure_locale(self):
129 129 if self.locale:
130 130 log.info('Settings locale: `LC_ALL` to %s', self.locale)
131 131 else:
132 132 log.info(
133 133 'Configuring locale subsystem based on environment variables')
134 134 try:
135 135 # If self.locale is the empty string, then the locale
136 136 # module will use the environment variables. See the
137 137 # documentation of the package `locale`.
138 138 locale.setlocale(locale.LC_ALL, self.locale)
139 139
140 140 language_code, encoding = locale.getlocale()
141 141 log.info(
142 142 'Locale set to language code "%s" with encoding "%s".',
143 143 language_code, encoding)
144 144 except locale.Error:
145 145 log.exception(
146 146 'Cannot set locale, not configuring the locale system')
147 147
148 148
149 149 class WsgiProxy(object):
150 150 def __init__(self, wsgi):
151 151 self.wsgi = wsgi
152 152
153 153 def __call__(self, environ, start_response):
154 154 input_data = environ['wsgi.input'].read()
155 155 input_data = msgpack.unpackb(input_data)
156 156
157 157 error = None
158 158 try:
159 159 data, status, headers = self.wsgi.handle(
160 160 input_data['environment'], input_data['input_data'],
161 161 *input_data['args'], **input_data['kwargs'])
162 162 except Exception as e:
163 163 data, status, headers = [], None, None
164 164 error = {
165 165 'message': str(e),
166 166 '_vcs_kind': getattr(e, '_vcs_kind', None)
167 167 }
168 168
169 169 start_response(200, {})
170 170 return self._iterator(error, status, headers, data)
171 171
172 172 def _iterator(self, error, status, headers, data):
173 173 initial_data = [
174 174 error,
175 175 status,
176 176 headers,
177 177 ]
178 178
179 179 for d in chain(initial_data, data):
180 180 yield msgpack.packb(d)
181 181
182 182
183 183 def not_found(request):
184 184 return {'status': '404 NOT FOUND'}
185 185
186 186
187 187 class VCSViewPredicate(object):
188 188 def __init__(self, val, config):
189 189 self.remotes = val
190 190
191 191 def text(self):
192 192 return 'vcs view method = %s' % (self.remotes.keys(),)
193 193
194 194 phash = text
195 195
196 196 def __call__(self, context, request):
197 197 """
198 198 View predicate that returns true if given backend is supported by
199 199 defined remotes.
200 200 """
201 201 backend = request.matchdict.get('backend')
202 202 return backend in self.remotes
203 203
204 204
205 205 class HTTPApplication(object):
206 206 ALLOWED_EXCEPTIONS = ('KeyError', 'URLError')
207 207
208 208 remote_wsgi = remote_wsgi
209 209 _use_echo_app = False
210 210
211 211 def __init__(self, settings=None, global_config=None):
212 212
213 213 self.config = Configurator(settings=settings)
214 214 # Init our statsd at very start
215 215 self.config.registry.statsd = StatsdClient.statsd
216 216
217 217 self.global_config = global_config
218 218 self.config.include('vcsserver.lib.rc_cache')
219 219
220 220 settings_locale = settings.get('locale', '') or 'en_US.UTF-8'
221 221 vcs = VCS(locale_conf=settings_locale, cache_config=settings)
222 222 self._remotes = {
223 223 'hg': vcs._hg_remote,
224 224 'git': vcs._git_remote,
225 225 'svn': vcs._svn_remote,
226 226 'server': vcs._vcsserver,
227 227 }
228 228 if settings.get('dev.use_echo_app', 'false').lower() == 'true':
229 229 self._use_echo_app = True
230 230 log.warning("Using EchoApp for VCS operations.")
231 231 self.remote_wsgi = remote_wsgi_stub
232 232
233 233 self._configure_settings(global_config, settings)
234 234
235 235 self._configure()
236 236
237 237 def _configure_settings(self, global_config, app_settings):
238 238 """
239 239 Configure the settings module.
240 240 """
241 241 settings_merged = global_config.copy()
242 242 settings_merged.update(app_settings)
243 243
244 244 git_path = app_settings.get('git_path', None)
245 245 if git_path:
246 246 settings.GIT_EXECUTABLE = git_path
247 247 binary_dir = app_settings.get('core.binary_dir', None)
248 248 if binary_dir:
249 249 settings.BINARY_DIR = binary_dir
250 250
251 251 # Store the settings to make them available to other modules.
252 252 vcsserver.PYRAMID_SETTINGS = settings_merged
253 253 vcsserver.CONFIG = settings_merged
254 254
255 255 def _configure(self):
256 256 self.config.add_renderer(name='msgpack', factory=self._msgpack_renderer_factory)
257 257
258 258 self.config.add_route('service', '/_service')
259 259 self.config.add_route('status', '/status')
260 260 self.config.add_route('hg_proxy', '/proxy/hg')
261 261 self.config.add_route('git_proxy', '/proxy/git')
262 262
263 263 # rpc methods
264 264 self.config.add_route('vcs', '/{backend}')
265 265
266 266 # streaming rpc remote methods
267 267 self.config.add_route('vcs_stream', '/{backend}/stream')
268 268
269 269 # vcs operations clone/push as streaming
270 270 self.config.add_route('stream_git', '/stream/git/*repo_name')
271 271 self.config.add_route('stream_hg', '/stream/hg/*repo_name')
272 272
273 273 self.config.add_view(self.status_view, route_name='status', renderer='json')
274 274 self.config.add_view(self.service_view, route_name='service', renderer='msgpack')
275 275
276 276 self.config.add_view(self.hg_proxy(), route_name='hg_proxy')
277 277 self.config.add_view(self.git_proxy(), route_name='git_proxy')
278 278 self.config.add_view(self.vcs_view, route_name='vcs', renderer='msgpack',
279 279 vcs_view=self._remotes)
280 280 self.config.add_view(self.vcs_stream_view, route_name='vcs_stream',
281 281 vcs_view=self._remotes)
282 282
283 283 self.config.add_view(self.hg_stream(), route_name='stream_hg')
284 284 self.config.add_view(self.git_stream(), route_name='stream_git')
285 285
286 286 self.config.add_view_predicate('vcs_view', VCSViewPredicate)
287 287
288 288 self.config.add_notfound_view(not_found, renderer='json')
289 289
290 290 self.config.add_view(self.handle_vcs_exception, context=Exception)
291 291
292 292 self.config.add_tween(
293 293 'vcsserver.tweens.request_wrapper.RequestWrapperTween',
294 294 )
295 295 self.config.add_request_method(
296 296 'vcsserver.lib.request_counter.get_request_counter',
297 297 'request_count')
298 298
299 299 def wsgi_app(self):
300 300 return self.config.make_wsgi_app()
301 301
302 302 def _vcs_view_params(self, request):
303 303 remote = self._remotes[request.matchdict['backend']]
304 304 payload = msgpack.unpackb(request.body, use_list=True)
305 305 method = payload.get('method')
306 306 params = payload['params']
307 307 wire = params.get('wire')
308 308 args = params.get('args')
309 309 kwargs = params.get('kwargs')
310 310 context_uid = None
311 311
312 312 if wire:
313 313 try:
314 314 wire['context'] = context_uid = uuid.UUID(wire['context'])
315 315 except KeyError:
316 316 pass
317 317 args.insert(0, wire)
318 318 repo_state_uid = wire.get('repo_state_uid') if wire else None
319 319
320 320 # NOTE(marcink): trading complexity for slight performance
321 321 if log.isEnabledFor(logging.DEBUG):
322 322 no_args_methods = [
323 323
324 324 ]
325 325 if method in no_args_methods:
326 326 call_args = ''
327 327 else:
328 328 call_args = args[1:]
329 329
330 330 log.debug('Method requested:`%s` with args:%s kwargs:%s context_uid: %s, repo_state_uid:%s',
331 331 method, call_args, kwargs, context_uid, repo_state_uid)
332 332
333 333 statsd = request.registry.statsd
334 334 if statsd:
335 335 statsd.incr(
336 336 'vcsserver_method_total', tags=[
337 337 "method:{}".format(method),
338 338 ])
339 339 return payload, remote, method, args, kwargs
340 340
341 341 def vcs_view(self, request):
342 342
343 343 payload, remote, method, args, kwargs = self._vcs_view_params(request)
344 344 payload_id = payload.get('id')
345 345
346 346 try:
347 347 resp = getattr(remote, method)(*args, **kwargs)
348 348 except Exception as e:
349 349 exc_info = list(sys.exc_info())
350 350 exc_type, exc_value, exc_traceback = exc_info
351 351
352 352 org_exc = getattr(e, '_org_exc', None)
353 353 org_exc_name = None
354 354 org_exc_tb = ''
355 355 if org_exc:
356 356 org_exc_name = org_exc.__class__.__name__
357 357 org_exc_tb = getattr(e, '_org_exc_tb', '')
358 358 # replace our "faked" exception with our org
359 359 exc_info[0] = org_exc.__class__
360 360 exc_info[1] = org_exc
361 361
362 362 should_store_exc = True
363 363 if org_exc:
364 364 def get_exc_fqn(_exc_obj):
365 365 module_name = getattr(org_exc.__class__, '__module__', 'UNKNOWN')
366 366 return module_name + '.' + org_exc_name
367 367
368 368 exc_fqn = get_exc_fqn(org_exc)
369 369
370 370 if exc_fqn in ['mercurial.error.RepoLookupError',
371 371 'vcsserver.exceptions.RefNotFoundException']:
372 372 should_store_exc = False
373 373
374 374 if should_store_exc:
375 375 store_exception(id(exc_info), exc_info, request_path=request.path)
376 376
377 377 tb_info = ''.join(
378 378 traceback.format_exception(exc_type, exc_value, exc_traceback))
379 379
380 380 type_ = e.__class__.__name__
381 381 if type_ not in self.ALLOWED_EXCEPTIONS:
382 382 type_ = None
383 383
384 384 resp = {
385 385 'id': payload_id,
386 386 'error': {
387 387 'message': e.message,
388 388 'traceback': tb_info,
389 389 'org_exc': org_exc_name,
390 390 'org_exc_tb': org_exc_tb,
391 391 'type': type_
392 392 }
393 393 }
394 394
395 395 try:
396 396 resp['error']['_vcs_kind'] = getattr(e, '_vcs_kind', None)
397 397 except AttributeError:
398 398 pass
399 399 else:
400 400 resp = {
401 401 'id': payload_id,
402 402 'result': resp
403 403 }
404 404
405 405 return resp
406 406
407 407 def vcs_stream_view(self, request):
408 408 payload, remote, method, args, kwargs = self._vcs_view_params(request)
409 409 # this method has a stream: marker we remove it here
410 410 method = method.split('stream:')[-1]
411 411 chunk_size = safe_int(payload.get('chunk_size')) or 4096
412 412
413 413 try:
414 414 resp = getattr(remote, method)(*args, **kwargs)
415 415 except Exception as e:
416 416 raise
417 417
418 418 def get_chunked_data(method_resp):
419 419 stream = StringIO(method_resp)
420 420 while 1:
421 421 chunk = stream.read(chunk_size)
422 422 if not chunk:
423 423 break
424 424 yield chunk
425 425
426 426 response = Response(app_iter=get_chunked_data(resp))
427 427 response.content_type = 'application/octet-stream'
428 428
429 429 return response
430 430
431 431 def status_view(self, request):
432 432 import vcsserver
433 433 return {'status': 'OK', 'vcsserver_version': vcsserver.__version__,
434 434 'pid': os.getpid()}
435 435
436 436 def service_view(self, request):
437 437 import vcsserver
438 438
439 439 payload = msgpack.unpackb(request.body, use_list=True)
440 440 server_config, app_config = {}, {}
441 441
442 442 try:
443 443 path = self.global_config['__file__']
444 444 config = configparser.RawConfigParser()
445 445
446 446 config.read(path)
447 447
448 448 if config.has_section('server:main'):
449 449 server_config = dict(config.items('server:main'))
450 450 if config.has_section('app:main'):
451 451 app_config = dict(config.items('app:main'))
452 452
453 453 except Exception:
454 454 log.exception('Failed to read .ini file for display')
455 455
456 456 environ = os.environ.items()
457 457
458 458 resp = {
459 459 'id': payload.get('id'),
460 460 'result': dict(
461 461 version=vcsserver.__version__,
462 462 config=server_config,
463 463 app_config=app_config,
464 464 environ=environ,
465 465 payload=payload,
466 466 )
467 467 }
468 468 return resp
469 469
470 470 def _msgpack_renderer_factory(self, info):
471 471 def _render(value, system):
472 472 request = system.get('request')
473 473 if request is not None:
474 474 response = request.response
475 475 ct = response.content_type
476 476 if ct == response.default_content_type:
477 477 response.content_type = 'application/x-msgpack'
478 478 return msgpack.packb(value)
479 479 return _render
480 480
481 481 def set_env_from_config(self, environ, config):
482 482 dict_conf = {}
483 483 try:
484 484 for elem in config:
485 485 if elem[0] == 'rhodecode':
486 486 dict_conf = json.loads(elem[2])
487 487 break
488 488 except Exception:
489 489 log.exception('Failed to fetch SCM CONFIG')
490 490 return
491 491
492 492 username = dict_conf.get('username')
493 493 if username:
494 494 environ['REMOTE_USER'] = username
495 495 # mercurial specific, some extension api rely on this
496 496 environ['HGUSER'] = username
497 497
498 498 ip = dict_conf.get('ip')
499 499 if ip:
500 500 environ['REMOTE_HOST'] = ip
501 501
502 502 if _is_request_chunked(environ):
503 503 # set the compatibility flag for webob
504 504 environ['wsgi.input_terminated'] = True
505 505
506 506 def hg_proxy(self):
507 507 @wsgiapp
508 508 def _hg_proxy(environ, start_response):
509 509 app = WsgiProxy(self.remote_wsgi.HgRemoteWsgi())
510 510 return app(environ, start_response)
511 511 return _hg_proxy
512 512
513 513 def git_proxy(self):
514 514 @wsgiapp
515 515 def _git_proxy(environ, start_response):
516 516 app = WsgiProxy(self.remote_wsgi.GitRemoteWsgi())
517 517 return app(environ, start_response)
518 518 return _git_proxy
519 519
520 520 def hg_stream(self):
521 521 if self._use_echo_app:
522 522 @wsgiapp
523 523 def _hg_stream(environ, start_response):
524 524 app = EchoApp('fake_path', 'fake_name', None)
525 525 return app(environ, start_response)
526 526 return _hg_stream
527 527 else:
528 528 @wsgiapp
529 529 def _hg_stream(environ, start_response):
530 530 log.debug('http-app: handling hg stream')
531 531 repo_path = environ['HTTP_X_RC_REPO_PATH']
532 532 repo_name = environ['HTTP_X_RC_REPO_NAME']
533 533 packed_config = base64.b64decode(
534 534 environ['HTTP_X_RC_REPO_CONFIG'])
535 535 config = msgpack.unpackb(packed_config)
536 536 app = scm_app.create_hg_wsgi_app(
537 537 repo_path, repo_name, config)
538 538
539 539 # Consistent path information for hgweb
540 540 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
541 541 environ['REPO_NAME'] = repo_name
542 542 self.set_env_from_config(environ, config)
543 543
544 544 log.debug('http-app: starting app handler '
545 545 'with %s and process request', app)
546 546 return app(environ, ResponseFilter(start_response))
547 547 return _hg_stream
548 548
549 549 def git_stream(self):
550 550 if self._use_echo_app:
551 551 @wsgiapp
552 552 def _git_stream(environ, start_response):
553 553 app = EchoApp('fake_path', 'fake_name', None)
554 554 return app(environ, start_response)
555 555 return _git_stream
556 556 else:
557 557 @wsgiapp
558 558 def _git_stream(environ, start_response):
559 559 log.debug('http-app: handling git stream')
560 560 repo_path = environ['HTTP_X_RC_REPO_PATH']
561 561 repo_name = environ['HTTP_X_RC_REPO_NAME']
562 562 packed_config = base64.b64decode(
563 563 environ['HTTP_X_RC_REPO_CONFIG'])
564 564 config = msgpack.unpackb(packed_config)
565 565
566 566 environ['PATH_INFO'] = environ['HTTP_X_RC_PATH_INFO']
567 567 self.set_env_from_config(environ, config)
568 568
569 569 content_type = environ.get('CONTENT_TYPE', '')
570 570
571 571 path = environ['PATH_INFO']
572 572 is_lfs_request = GIT_LFS_CONTENT_TYPE in content_type
573 573 log.debug(
574 574 'LFS: Detecting if request `%s` is LFS server path based '
575 575 'on content type:`%s`, is_lfs:%s',
576 576 path, content_type, is_lfs_request)
577 577
578 578 if not is_lfs_request:
579 579 # fallback detection by path
580 580 if GIT_LFS_PROTO_PAT.match(path):
581 581 is_lfs_request = True
582 582 log.debug(
583 583 'LFS: fallback detection by path of: `%s`, is_lfs:%s',
584 584 path, is_lfs_request)
585 585
586 586 if is_lfs_request:
587 587 app = scm_app.create_git_lfs_wsgi_app(
588 588 repo_path, repo_name, config)
589 589 else:
590 590 app = scm_app.create_git_wsgi_app(
591 591 repo_path, repo_name, config)
592 592
593 593 log.debug('http-app: starting app handler '
594 594 'with %s and process request', app)
595 595
596 596 return app(environ, start_response)
597 597
598 598 return _git_stream
599 599
600 600 def handle_vcs_exception(self, exception, request):
601 601 _vcs_kind = getattr(exception, '_vcs_kind', '')
602 602 if _vcs_kind == 'repo_locked':
603 603 # Get custom repo-locked status code if present.
604 604 status_code = request.headers.get('X-RC-Locked-Status-Code')
605 605 return HTTPRepoLocked(
606 606 title=exception.message, status_code=status_code)
607 607
608 608 elif _vcs_kind == 'repo_branch_protected':
609 609 # Get custom repo-branch-protected status code if present.
610 610 return HTTPRepoBranchProtected(title=exception.message)
611 611
612 612 exc_info = request.exc_info
613 613 store_exception(id(exc_info), exc_info)
614 614
615 615 traceback_info = 'unavailable'
616 616 if request.exc_info:
617 617 exc_type, exc_value, exc_tb = request.exc_info
618 618 traceback_info = ''.join(traceback.format_exception(exc_type, exc_value, exc_tb))
619 619
620 620 log.error(
621 621 'error occurred handling this request for path: %s, \n tb: %s',
622 622 request.path, traceback_info)
623 623
624 624 statsd = request.registry.statsd
625 625 if statsd:
626 626 exc_type = "{}.{}".format(exception.__class__.__module__, exception.__class__.__name__)
627 627 statsd.incr('vcsserver_exception_total',
628 628 tags=["type:{}".format(exc_type)])
629 629 raise exception
630 630
631 631
632 632 class ResponseFilter(object):
633 633
634 634 def __init__(self, start_response):
635 635 self._start_response = start_response
636 636
637 637 def __call__(self, status, response_headers, exc_info=None):
638 638 headers = tuple(
639 639 (h, v) for h, v in response_headers
640 640 if not wsgiref.util.is_hop_by_hop(h))
641 641 return self._start_response(status, headers, exc_info)
642 642
643 643
644 644 def sanitize_settings_and_apply_defaults(global_config, settings):
645 645 global_settings_maker = SettingsMaker(global_config)
646 646 settings_maker = SettingsMaker(settings)
647 647
648 settings_maker.make_setting(
649 'logging.autoconfigure',
650 default=True,
651 parser='bool')
648 settings_maker.make_setting('logging.autoconfigure', True, parser='bool')
652 649
653 650 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
654 651 settings_maker.enable_logging(logging_conf)
655 652
656 653 # Default includes, possible to change as a user
657 654 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
658 log.debug(
659 "Using the following pyramid.includes: %s",
660 pyramid_includes)
655 log.debug("Using the following pyramid.includes: %s", pyramid_includes)
661 656
662 657 settings_maker.make_setting('__file__', global_config.get('__file__'))
663 658
664 settings_maker.make_setting(
665 'pyramid.default_locale_name',
666 default='en',
667 parser='string')
668 settings_maker.make_setting(
669 'locale',
670 default='en_US.UTF-8',
671 parser='string')
659 settings_maker.make_setting('pyramid.default_locale_name', 'en')
660 settings_maker.make_setting('locale', 'en_US.UTF-8')
672 661
673 settings_maker.make_setting(
674 'core.binary_dir',
675 default='',
676 parser='string')
662 settings_maker.make_setting('core.binary_dir', '')
677 663
678 664 temp_store = tempfile.gettempdir()
679 665 default_cache_dir = os.path.join(temp_store, 'rc_cache')
680 666 # save default, cache dir, and use it for all backends later.
681 667 default_cache_dir = settings_maker.make_setting(
682 668 'cache_dir',
683 669 default=default_cache_dir, default_when_empty=True,
684 670 parser='dir:ensured')
685 671
686 672 # exception store cache
687 673 settings_maker.make_setting(
688 674 'exception_tracker.store_path',
689 675 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
690 676 parser='dir:ensured'
691 677 )
692 678
693 679 # repo_object cache defaults
694 680 settings_maker.make_setting(
695 681 'rc_cache.repo_object.backend',
696 682 default='dogpile.cache.rc.file_namespace',
697 683 parser='string')
698 684 settings_maker.make_setting(
699 685 'rc_cache.repo_object.expiration_time',
700 686 default=30 * 24 * 60 * 60, # 30days
701 687 parser='int')
702 settings_maker. make_setting(
688 settings_maker.make_setting(
703 689 'rc_cache.repo_object.arguments.filename',
704 690 default=os.path.join(default_cache_dir, 'vcsserver_cache_repo_object.db'),
705 691 parser='string')
706 692
707 693 # statsd
708 settings_maker. make_setting(
709 'statsd.enabled',
710 default=False,
711 parser='bool')
712 settings_maker. make_setting(
713 'statsd.statsd_host',
714 default='statsd-exporter',
715 parser='string')
716 settings_maker. make_setting(
717 'statsd.statsd_port',
718 default=9125,
719 parser='int')
720 settings_maker. make_setting(
721 'statsd.statsd_prefix',
722 default='',
723 parser='string')
724 settings_maker. make_setting(
725 'statsd.statsd_ipv6',
726 default=False,
727 parser='bool')
694 settings_maker.make_setting('statsd.enabled', False, parser='bool')
695 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
696 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
697 settings_maker.make_setting('statsd.statsd_prefix', '')
698 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
699
700 settings_maker.env_expand()
728 701
729 702
730 703 def main(global_config, **settings):
731 704 start_time = time.time()
732 705 log.info('Pyramid app config starting')
733 706
734 707 if MercurialFactory:
735 708 hgpatches.patch_largefiles_capabilities()
736 709 hgpatches.patch_subrepo_type_mapping()
737 710
738 711 # Fill in and sanitize the defaults & do ENV expansion
739 712 sanitize_settings_and_apply_defaults(global_config, settings)
740 713
741 714 # init and bootstrap StatsdClient
742 715 StatsdClient.setup(settings)
743 716
744 717 pyramid_app = HTTPApplication(settings=settings, global_config=global_config).wsgi_app()
745 718 total_time = time.time() - start_time
746 719 log.info('Pyramid app `%s` created and configured in %.2fs',
747 720 getattr(pyramid_app, 'func_name', 'pyramid_app'), total_time)
748 721 return pyramid_app
749 722
750 723
General Comments 0
You need to be logged in to leave comments. Login now