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