Show More
@@ -0,0 +1,198 b'' | |||||
|
1 | # Copyright (C) 2010-2023 RhodeCode GmbH | |||
|
2 | # | |||
|
3 | # This program is free software: you can redistribute it and/or modify | |||
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
5 | # (only), as published by the Free Software Foundation. | |||
|
6 | # | |||
|
7 | # This program is distributed in the hope that it will be useful, | |||
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
10 | # GNU General Public License for more details. | |||
|
11 | # | |||
|
12 | # You should have received a copy of the GNU Affero General Public License | |||
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
14 | # | |||
|
15 | # This program is dual-licensed. If you wish to learn more about the | |||
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
18 | ||||
|
19 | import os | |||
|
20 | import tempfile | |||
|
21 | import logging | |||
|
22 | ||||
|
23 | from pyramid.settings import asbool | |||
|
24 | ||||
|
25 | from rhodecode.config.settings_maker import SettingsMaker | |||
|
26 | from rhodecode.config import utils as config_utils | |||
|
27 | ||||
|
28 | log = logging.getLogger(__name__) | |||
|
29 | ||||
|
30 | ||||
|
31 | def sanitize_settings_and_apply_defaults(global_config, settings): | |||
|
32 | """ | |||
|
33 | Applies settings defaults and does all type conversion. | |||
|
34 | ||||
|
35 | We would move all settings parsing and preparation into this place, so that | |||
|
36 | we have only one place left which deals with this part. The remaining parts | |||
|
37 | of the application would start to rely fully on well-prepared settings. | |||
|
38 | ||||
|
39 | This piece would later be split up per topic to avoid a big fat monster | |||
|
40 | function. | |||
|
41 | """ | |||
|
42 | jn = os.path.join | |||
|
43 | ||||
|
44 | global_settings_maker = SettingsMaker(global_config) | |||
|
45 | global_settings_maker.make_setting('debug', default=False, parser='bool') | |||
|
46 | debug_enabled = asbool(global_config.get('debug')) | |||
|
47 | ||||
|
48 | settings_maker = SettingsMaker(settings) | |||
|
49 | ||||
|
50 | settings_maker.make_setting( | |||
|
51 | 'logging.autoconfigure', | |||
|
52 | default=False, | |||
|
53 | parser='bool') | |||
|
54 | ||||
|
55 | logging_conf = jn(os.path.dirname(global_config.get('__file__')), 'logging.ini') | |||
|
56 | settings_maker.enable_logging(logging_conf, level='INFO' if debug_enabled else 'DEBUG') | |||
|
57 | ||||
|
58 | # Default includes, possible to change as a user | |||
|
59 | pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline') | |||
|
60 | log.debug( | |||
|
61 | "Using the following pyramid.includes: %s", | |||
|
62 | pyramid_includes) | |||
|
63 | ||||
|
64 | settings_maker.make_setting('rhodecode.edition', 'Community Edition') | |||
|
65 | settings_maker.make_setting('rhodecode.edition_id', 'CE') | |||
|
66 | ||||
|
67 | if 'mako.default_filters' not in settings: | |||
|
68 | # set custom default filters if we don't have it defined | |||
|
69 | settings['mako.imports'] = 'from rhodecode.lib.base import h_filter' | |||
|
70 | settings['mako.default_filters'] = 'h_filter' | |||
|
71 | ||||
|
72 | if 'mako.directories' not in settings: | |||
|
73 | mako_directories = settings.setdefault('mako.directories', [ | |||
|
74 | # Base templates of the original application | |||
|
75 | 'rhodecode:templates', | |||
|
76 | ]) | |||
|
77 | log.debug( | |||
|
78 | "Using the following Mako template directories: %s", | |||
|
79 | mako_directories) | |||
|
80 | ||||
|
81 | # NOTE(marcink): fix redis requirement for schema of connection since 3.X | |||
|
82 | if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis': | |||
|
83 | raw_url = settings['beaker.session.url'] | |||
|
84 | if not raw_url.startswith(('redis://', 'rediss://', 'unix://')): | |||
|
85 | settings['beaker.session.url'] = 'redis://' + raw_url | |||
|
86 | ||||
|
87 | settings_maker.make_setting('__file__', global_config.get('__file__')) | |||
|
88 | ||||
|
89 | # TODO: johbo: Re-think this, usually the call to config.include | |||
|
90 | # should allow to pass in a prefix. | |||
|
91 | settings_maker.make_setting('rhodecode.api.url', '/_admin/api') | |||
|
92 | ||||
|
93 | # Sanitize generic settings. | |||
|
94 | settings_maker.make_setting('default_encoding', 'UTF-8', parser='list') | |||
|
95 | settings_maker.make_setting('is_test', False, parser='bool') | |||
|
96 | settings_maker.make_setting('gzip_responses', False, parser='bool') | |||
|
97 | ||||
|
98 | # statsd | |||
|
99 | settings_maker.make_setting('statsd.enabled', False, parser='bool') | |||
|
100 | settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string') | |||
|
101 | settings_maker.make_setting('statsd.statsd_port', 9125, parser='int') | |||
|
102 | settings_maker.make_setting('statsd.statsd_prefix', '') | |||
|
103 | settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool') | |||
|
104 | ||||
|
105 | settings_maker.make_setting('vcs.svn.compatible_version', '') | |||
|
106 | settings_maker.make_setting('vcs.hooks.protocol', 'http') | |||
|
107 | settings_maker.make_setting('vcs.hooks.host', '*') | |||
|
108 | settings_maker.make_setting('vcs.scm_app_implementation', 'http') | |||
|
109 | settings_maker.make_setting('vcs.server', '') | |||
|
110 | settings_maker.make_setting('vcs.server.protocol', 'http') | |||
|
111 | settings_maker.make_setting('vcs.server.enable', 'true', parser='bool') | |||
|
112 | settings_maker.make_setting('startup.import_repos', 'false', parser='bool') | |||
|
113 | settings_maker.make_setting('vcs.hooks.direct_calls', 'false', parser='bool') | |||
|
114 | settings_maker.make_setting('vcs.start_server', 'false', parser='bool') | |||
|
115 | settings_maker.make_setting('vcs.backends', 'hg, git, svn', parser='list') | |||
|
116 | settings_maker.make_setting('vcs.connection_timeout', 3600, parser='int') | |||
|
117 | ||||
|
118 | settings_maker.make_setting('vcs.methods.cache', True, parser='bool') | |||
|
119 | ||||
|
120 | # Support legacy values of vcs.scm_app_implementation. Legacy | |||
|
121 | # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or | |||
|
122 | # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'. | |||
|
123 | scm_app_impl = settings['vcs.scm_app_implementation'] | |||
|
124 | if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']: | |||
|
125 | settings['vcs.scm_app_implementation'] = 'http' | |||
|
126 | ||||
|
127 | settings_maker.make_setting('appenlight', False, parser='bool') | |||
|
128 | ||||
|
129 | temp_store = tempfile.gettempdir() | |||
|
130 | tmp_cache_dir = jn(temp_store, 'rc_cache') | |||
|
131 | ||||
|
132 | # save default, cache dir, and use it for all backends later. | |||
|
133 | default_cache_dir = settings_maker.make_setting( | |||
|
134 | 'cache_dir', | |||
|
135 | default=tmp_cache_dir, default_when_empty=True, | |||
|
136 | parser='dir:ensured') | |||
|
137 | ||||
|
138 | # exception store cache | |||
|
139 | settings_maker.make_setting( | |||
|
140 | 'exception_tracker.store_path', | |||
|
141 | default=jn(default_cache_dir, 'exc_store'), default_when_empty=True, | |||
|
142 | parser='dir:ensured' | |||
|
143 | ) | |||
|
144 | ||||
|
145 | settings_maker.make_setting( | |||
|
146 | 'celerybeat-schedule.path', | |||
|
147 | default=jn(default_cache_dir, 'celerybeat_schedule', 'celerybeat-schedule.db'), default_when_empty=True, | |||
|
148 | parser='file:ensured' | |||
|
149 | ) | |||
|
150 | ||||
|
151 | settings_maker.make_setting('exception_tracker.send_email', False, parser='bool') | |||
|
152 | settings_maker.make_setting('exception_tracker.email_prefix', '[RHODECODE ERROR]', default_when_empty=True) | |||
|
153 | ||||
|
154 | # sessions, ensure file since no-value is memory | |||
|
155 | settings_maker.make_setting('beaker.session.type', 'file') | |||
|
156 | settings_maker.make_setting('beaker.session.data_dir', jn(default_cache_dir, 'session_data')) | |||
|
157 | ||||
|
158 | # cache_general | |||
|
159 | settings_maker.make_setting('rc_cache.cache_general.backend', 'dogpile.cache.rc.file_namespace') | |||
|
160 | settings_maker.make_setting('rc_cache.cache_general.expiration_time', 60 * 60 * 12, parser='int') | |||
|
161 | settings_maker.make_setting('rc_cache.cache_general.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_general.db')) | |||
|
162 | ||||
|
163 | # cache_perms | |||
|
164 | settings_maker.make_setting('rc_cache.cache_perms.backend', 'dogpile.cache.rc.file_namespace') | |||
|
165 | settings_maker.make_setting('rc_cache.cache_perms.expiration_time', 60 * 60, parser='int') | |||
|
166 | settings_maker.make_setting('rc_cache.cache_perms.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_perms_db')) | |||
|
167 | ||||
|
168 | # cache_repo | |||
|
169 | settings_maker.make_setting('rc_cache.cache_repo.backend', 'dogpile.cache.rc.file_namespace') | |||
|
170 | settings_maker.make_setting('rc_cache.cache_repo.expiration_time', 60 * 60 * 24 * 30, parser='int') | |||
|
171 | settings_maker.make_setting('rc_cache.cache_repo.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_repo_db')) | |||
|
172 | ||||
|
173 | # cache_license | |||
|
174 | settings_maker.make_setting('rc_cache.cache_license.backend', 'dogpile.cache.rc.file_namespace') | |||
|
175 | settings_maker.make_setting('rc_cache.cache_license.expiration_time', 60 * 5, parser='int') | |||
|
176 | settings_maker.make_setting('rc_cache.cache_license.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_license_db')) | |||
|
177 | ||||
|
178 | # cache_repo_longterm memory, 96H | |||
|
179 | settings_maker.make_setting('rc_cache.cache_repo_longterm.backend', 'dogpile.cache.rc.memory_lru') | |||
|
180 | settings_maker.make_setting('rc_cache.cache_repo_longterm.expiration_time', 345600, parser='int') | |||
|
181 | settings_maker.make_setting('rc_cache.cache_repo_longterm.max_size', 10000, parser='int') | |||
|
182 | ||||
|
183 | # sql_cache_short | |||
|
184 | settings_maker.make_setting('rc_cache.sql_cache_short.backend', 'dogpile.cache.rc.memory_lru') | |||
|
185 | settings_maker.make_setting('rc_cache.sql_cache_short.expiration_time', 30, parser='int') | |||
|
186 | settings_maker.make_setting('rc_cache.sql_cache_short.max_size', 10000, parser='int') | |||
|
187 | ||||
|
188 | # archive_cache | |||
|
189 | settings_maker.make_setting('archive_cache.store_dir', jn(default_cache_dir, 'archive_cache'), default_when_empty=True,) | |||
|
190 | settings_maker.make_setting('archive_cache.cache_size_gb', 10, parser='float') | |||
|
191 | settings_maker.make_setting('archive_cache.cache_shards', 10, parser='int') | |||
|
192 | ||||
|
193 | settings_maker.env_expand() | |||
|
194 | ||||
|
195 | # configure instance id | |||
|
196 | config_utils.set_instance_id(settings) | |||
|
197 | ||||
|
198 | return settings |
@@ -0,0 +1,38 b'' | |||||
|
1 | # Copyright (C) 2010-2023 RhodeCode GmbH | |||
|
2 | # | |||
|
3 | # This program is free software: you can redistribute it and/or modify | |||
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
5 | # (only), as published by the Free Software Foundation. | |||
|
6 | # | |||
|
7 | # This program is distributed in the hope that it will be useful, | |||
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
10 | # GNU General Public License for more details. | |||
|
11 | # | |||
|
12 | # You should have received a copy of the GNU Affero General Public License | |||
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
14 | # | |||
|
15 | # This program is dual-licensed. If you wish to learn more about the | |||
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
18 | ||||
|
19 | import urllib.parse | |||
|
20 | ||||
|
21 | from rhodecode.lib.vcs import CurlSession | |||
|
22 | from rhodecode.lib.ext_json import json | |||
|
23 | ||||
|
24 | ||||
|
25 | def call_service_api(service_api_host, service_api_token, api_url, payload): | |||
|
26 | ||||
|
27 | payload.update({ | |||
|
28 | 'id': 'service', | |||
|
29 | 'auth_token': service_api_token | |||
|
30 | }) | |||
|
31 | ||||
|
32 | service_api_url = urllib.parse.urljoin(service_api_host, api_url) | |||
|
33 | response = CurlSession().post(service_api_url, json.dumps(payload)) | |||
|
34 | ||||
|
35 | if response.status_code != 200: | |||
|
36 | raise Exception(f"Service API at {service_api_url} responded with error: {response.status_code}") | |||
|
37 | ||||
|
38 | return json.loads(response.content)['result'] |
@@ -0,0 +1,40 b'' | |||||
|
1 | # Copyright (C) 2010-2023 RhodeCode GmbH | |||
|
2 | # | |||
|
3 | # This program is free software: you can redistribute it and/or modify | |||
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
5 | # (only), as published by the Free Software Foundation. | |||
|
6 | # | |||
|
7 | # This program is distributed in the hope that it will be useful, | |||
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
10 | # GNU General Public License for more details. | |||
|
11 | # | |||
|
12 | # You should have received a copy of the GNU Affero General Public License | |||
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
14 | # | |||
|
15 | # This program is dual-licensed. If you wish to learn more about the | |||
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
18 | import os | |||
|
19 | ||||
|
20 | ||||
|
21 | def get_config(ini_path, **kwargs): | |||
|
22 | import configparser | |||
|
23 | parser = configparser.ConfigParser(**kwargs) | |||
|
24 | parser.read(ini_path) | |||
|
25 | return parser | |||
|
26 | ||||
|
27 | ||||
|
28 | def get_app_config_lightweight(ini_path): | |||
|
29 | parser = get_config(ini_path) | |||
|
30 | parser.set('app:main', 'here', os.getcwd()) | |||
|
31 | parser.set('app:main', '__file__', ini_path) | |||
|
32 | return dict(parser.items('app:main')) | |||
|
33 | ||||
|
34 | ||||
|
35 | def get_app_config(ini_path): | |||
|
36 | """ | |||
|
37 | This loads the app context and provides a heavy type iniliaziation of config | |||
|
38 | """ | |||
|
39 | from paste.deploy.loadwsgi import appconfig | |||
|
40 | return appconfig(f'config:{ini_path}', relative_to=os.getcwd()) |
@@ -0,0 +1,17 b'' | |||||
|
1 | # Copyright (C) 2010-2023 RhodeCode GmbH | |||
|
2 | # | |||
|
3 | # This program is free software: you can redistribute it and/or modify | |||
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
5 | # (only), as published by the Free Software Foundation. | |||
|
6 | # | |||
|
7 | # This program is distributed in the hope that it will be useful, | |||
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
10 | # GNU General Public License for more details. | |||
|
11 | # | |||
|
12 | # You should have received a copy of the GNU Affero General Public License | |||
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
14 | # | |||
|
15 | # This program is dual-licensed. If you wish to learn more about the | |||
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
@@ -0,0 +1,115 b'' | |||||
|
1 | # Copyright (C) 2010-2023 RhodeCode GmbH | |||
|
2 | # | |||
|
3 | # This program is free software: you can redistribute it and/or modify | |||
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
5 | # (only), as published by the Free Software Foundation. | |||
|
6 | # | |||
|
7 | # This program is distributed in the hope that it will be useful, | |||
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
10 | # GNU General Public License for more details. | |||
|
11 | # | |||
|
12 | # You should have received a copy of the GNU Affero General Public License | |||
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
14 | # | |||
|
15 | # This program is dual-licensed. If you wish to learn more about the | |||
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
18 | import os | |||
|
19 | import time | |||
|
20 | import logging | |||
|
21 | import tempfile | |||
|
22 | ||||
|
23 | from rhodecode.lib.config_utils import get_config | |||
|
24 | from rhodecode.lib.ext_json import json | |||
|
25 | ||||
|
26 | log = logging.getLogger(__name__) | |||
|
27 | ||||
|
28 | ||||
|
29 | class BaseHooksCallbackDaemon: | |||
|
30 | """ | |||
|
31 | Basic context manager for actions that don't require some extra | |||
|
32 | """ | |||
|
33 | def __init__(self): | |||
|
34 | pass | |||
|
35 | ||||
|
36 | def __enter__(self): | |||
|
37 | log.debug('Running `%s` callback daemon', self.__class__.__name__) | |||
|
38 | return self | |||
|
39 | ||||
|
40 | def __exit__(self, exc_type, exc_val, exc_tb): | |||
|
41 | log.debug('Exiting `%s` callback daemon', self.__class__.__name__) | |||
|
42 | ||||
|
43 | ||||
|
44 | class HooksModuleCallbackDaemon(BaseHooksCallbackDaemon): | |||
|
45 | ||||
|
46 | def __init__(self, module): | |||
|
47 | super().__init__() | |||
|
48 | self.hooks_module = module | |||
|
49 | ||||
|
50 | ||||
|
51 | def get_txn_id_data_path(txn_id): | |||
|
52 | import rhodecode | |||
|
53 | ||||
|
54 | root = rhodecode.CONFIG.get('cache_dir') or tempfile.gettempdir() | |||
|
55 | final_dir = os.path.join(root, 'svn_txn_id') | |||
|
56 | ||||
|
57 | if not os.path.isdir(final_dir): | |||
|
58 | os.makedirs(final_dir) | |||
|
59 | return os.path.join(final_dir, 'rc_txn_id_{}'.format(txn_id)) | |||
|
60 | ||||
|
61 | ||||
|
62 | def store_txn_id_data(txn_id, data_dict): | |||
|
63 | if not txn_id: | |||
|
64 | log.warning('Cannot store txn_id because it is empty') | |||
|
65 | return | |||
|
66 | ||||
|
67 | path = get_txn_id_data_path(txn_id) | |||
|
68 | try: | |||
|
69 | with open(path, 'wb') as f: | |||
|
70 | f.write(json.dumps(data_dict)) | |||
|
71 | except Exception: | |||
|
72 | log.exception('Failed to write txn_id metadata') | |||
|
73 | ||||
|
74 | ||||
|
75 | def get_txn_id_from_store(txn_id): | |||
|
76 | """ | |||
|
77 | Reads txn_id from store and if present returns the data for callback manager | |||
|
78 | """ | |||
|
79 | path = get_txn_id_data_path(txn_id) | |||
|
80 | try: | |||
|
81 | with open(path, 'rb') as f: | |||
|
82 | return json.loads(f.read()) | |||
|
83 | except Exception: | |||
|
84 | return {} | |||
|
85 | ||||
|
86 | ||||
|
87 | def prepare_callback_daemon(extras, protocol, host, txn_id=None): | |||
|
88 | txn_details = get_txn_id_from_store(txn_id) | |||
|
89 | port = txn_details.get('port', 0) | |||
|
90 | match protocol: | |||
|
91 | case 'http': | |||
|
92 | from rhodecode.lib.hook_daemon.http_hooks_deamon import HttpHooksCallbackDaemon | |||
|
93 | callback_daemon = HttpHooksCallbackDaemon( | |||
|
94 | txn_id=txn_id, host=host, port=port) | |||
|
95 | case 'celery': | |||
|
96 | from rhodecode.lib.hook_daemon.celery_hooks_deamon import CeleryHooksCallbackDaemon | |||
|
97 | callback_daemon = CeleryHooksCallbackDaemon(get_config(extras['config'])) | |||
|
98 | case 'local': | |||
|
99 | from rhodecode.lib.hook_daemon.hook_module import Hooks | |||
|
100 | callback_daemon = HooksModuleCallbackDaemon(Hooks.__module__) | |||
|
101 | case _: | |||
|
102 | log.error('Unsupported callback daemon protocol "%s"', protocol) | |||
|
103 | raise Exception('Unsupported callback daemon protocol.') | |||
|
104 | ||||
|
105 | extras['hooks_uri'] = getattr(callback_daemon, 'hooks_uri', '') | |||
|
106 | extras['task_queue'] = getattr(callback_daemon, 'task_queue', '') | |||
|
107 | extras['task_backend'] = getattr(callback_daemon, 'task_backend', '') | |||
|
108 | extras['hooks_protocol'] = protocol | |||
|
109 | extras['time'] = time.time() | |||
|
110 | ||||
|
111 | # register txn_id | |||
|
112 | extras['txn_id'] = txn_id | |||
|
113 | log.debug('Prepared a callback daemon: %s', | |||
|
114 | callback_daemon.__class__.__name__) | |||
|
115 | return callback_daemon, extras |
@@ -0,0 +1,30 b'' | |||||
|
1 | # Copyright (C) 2010-2023 RhodeCode GmbH | |||
|
2 | # | |||
|
3 | # This program is free software: you can redistribute it and/or modify | |||
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
5 | # (only), as published by the Free Software Foundation. | |||
|
6 | # | |||
|
7 | # This program is distributed in the hope that it will be useful, | |||
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
10 | # GNU General Public License for more details. | |||
|
11 | # | |||
|
12 | # You should have received a copy of the GNU Affero General Public License | |||
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
14 | # | |||
|
15 | # This program is dual-licensed. If you wish to learn more about the | |||
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
18 | ||||
|
19 | from rhodecode.lib.hook_daemon.base import BaseHooksCallbackDaemon | |||
|
20 | ||||
|
21 | ||||
|
22 | class CeleryHooksCallbackDaemon(BaseHooksCallbackDaemon): | |||
|
23 | """ | |||
|
24 | Context manger for achieving a compatibility with celery backend | |||
|
25 | """ | |||
|
26 | ||||
|
27 | def __init__(self, config): | |||
|
28 | # TODO: replace this with settings bootstrapped... | |||
|
29 | self.task_queue = config.get('app:main', 'celery.broker_url') | |||
|
30 | self.task_backend = config.get('app:main', 'celery.result_backend') |
@@ -0,0 +1,104 b'' | |||||
|
1 | # Copyright (C) 2010-2023 RhodeCode GmbH | |||
|
2 | # | |||
|
3 | # This program is free software: you can redistribute it and/or modify | |||
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
5 | # (only), as published by the Free Software Foundation. | |||
|
6 | # | |||
|
7 | # This program is distributed in the hope that it will be useful, | |||
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
10 | # GNU General Public License for more details. | |||
|
11 | # | |||
|
12 | # You should have received a copy of the GNU Affero General Public License | |||
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
14 | # | |||
|
15 | # This program is dual-licensed. If you wish to learn more about the | |||
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
18 | ||||
|
19 | import logging | |||
|
20 | import traceback | |||
|
21 | ||||
|
22 | from rhodecode.model import meta | |||
|
23 | ||||
|
24 | from rhodecode.lib import hooks_base | |||
|
25 | from rhodecode.lib.exceptions import HTTPLockedRC, HTTPBranchProtected | |||
|
26 | from rhodecode.lib.utils2 import AttributeDict | |||
|
27 | ||||
|
28 | log = logging.getLogger(__name__) | |||
|
29 | ||||
|
30 | ||||
|
31 | class Hooks(object): | |||
|
32 | """ | |||
|
33 | Exposes the hooks for remote callbacks | |||
|
34 | """ | |||
|
35 | def __init__(self, request=None, log_prefix=''): | |||
|
36 | self.log_prefix = log_prefix | |||
|
37 | self.request = request | |||
|
38 | ||||
|
39 | def repo_size(self, extras): | |||
|
40 | log.debug("%sCalled repo_size of %s object", self.log_prefix, self) | |||
|
41 | return self._call_hook(hooks_base.repo_size, extras) | |||
|
42 | ||||
|
43 | def pre_pull(self, extras): | |||
|
44 | log.debug("%sCalled pre_pull of %s object", self.log_prefix, self) | |||
|
45 | return self._call_hook(hooks_base.pre_pull, extras) | |||
|
46 | ||||
|
47 | def post_pull(self, extras): | |||
|
48 | log.debug("%sCalled post_pull of %s object", self.log_prefix, self) | |||
|
49 | return self._call_hook(hooks_base.post_pull, extras) | |||
|
50 | ||||
|
51 | def pre_push(self, extras): | |||
|
52 | log.debug("%sCalled pre_push of %s object", self.log_prefix, self) | |||
|
53 | return self._call_hook(hooks_base.pre_push, extras) | |||
|
54 | ||||
|
55 | def post_push(self, extras): | |||
|
56 | log.debug("%sCalled post_push of %s object", self.log_prefix, self) | |||
|
57 | return self._call_hook(hooks_base.post_push, extras) | |||
|
58 | ||||
|
59 | def _call_hook(self, hook, extras): | |||
|
60 | extras = AttributeDict(extras) | |||
|
61 | _server_url = extras['server_url'] | |||
|
62 | ||||
|
63 | extras.request = self.request | |||
|
64 | ||||
|
65 | try: | |||
|
66 | result = hook(extras) | |||
|
67 | if result is None: | |||
|
68 | raise Exception(f'Failed to obtain hook result from func: {hook}') | |||
|
69 | except HTTPBranchProtected as handled_error: | |||
|
70 | # Those special cases don't need error reporting. It's a case of | |||
|
71 | # locked repo or protected branch | |||
|
72 | result = AttributeDict({ | |||
|
73 | 'status': handled_error.code, | |||
|
74 | 'output': handled_error.explanation | |||
|
75 | }) | |||
|
76 | except (HTTPLockedRC, Exception) as error: | |||
|
77 | # locked needs different handling since we need to also | |||
|
78 | # handle PULL operations | |||
|
79 | exc_tb = '' | |||
|
80 | if not isinstance(error, HTTPLockedRC): | |||
|
81 | exc_tb = traceback.format_exc() | |||
|
82 | log.exception('%sException when handling hook %s', self.log_prefix, hook) | |||
|
83 | error_args = error.args | |||
|
84 | return { | |||
|
85 | 'status': 128, | |||
|
86 | 'output': '', | |||
|
87 | 'exception': type(error).__name__, | |||
|
88 | 'exception_traceback': exc_tb, | |||
|
89 | 'exception_args': error_args, | |||
|
90 | } | |||
|
91 | finally: | |||
|
92 | meta.Session.remove() | |||
|
93 | ||||
|
94 | log.debug('%sGot hook call response %s', self.log_prefix, result) | |||
|
95 | return { | |||
|
96 | 'status': result.status, | |||
|
97 | 'output': result.output, | |||
|
98 | } | |||
|
99 | ||||
|
100 | def __enter__(self): | |||
|
101 | return self | |||
|
102 | ||||
|
103 | def __exit__(self, exc_type, exc_val, exc_tb): | |||
|
104 | pass |
@@ -0,0 +1,280 b'' | |||||
|
1 | # Copyright (C) 2010-2023 RhodeCode GmbH | |||
|
2 | # | |||
|
3 | # This program is free software: you can redistribute it and/or modify | |||
|
4 | # it under the terms of the GNU Affero General Public License, version 3 | |||
|
5 | # (only), as published by the Free Software Foundation. | |||
|
6 | # | |||
|
7 | # This program is distributed in the hope that it will be useful, | |||
|
8 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
|
9 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
|
10 | # GNU General Public License for more details. | |||
|
11 | # | |||
|
12 | # You should have received a copy of the GNU Affero General Public License | |||
|
13 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
|
14 | # | |||
|
15 | # This program is dual-licensed. If you wish to learn more about the | |||
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |||
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |||
|
18 | ||||
|
19 | import os | |||
|
20 | import logging | |||
|
21 | import traceback | |||
|
22 | import threading | |||
|
23 | import socket | |||
|
24 | import msgpack | |||
|
25 | import gevent | |||
|
26 | ||||
|
27 | from http.server import BaseHTTPRequestHandler | |||
|
28 | from socketserver import TCPServer | |||
|
29 | ||||
|
30 | from rhodecode.model import meta | |||
|
31 | from rhodecode.lib.ext_json import json | |||
|
32 | from rhodecode.lib import rc_cache | |||
|
33 | from rhodecode.lib.hook_daemon.base import get_txn_id_data_path | |||
|
34 | from rhodecode.lib.hook_daemon.hook_module import Hooks | |||
|
35 | ||||
|
36 | log = logging.getLogger(__name__) | |||
|
37 | ||||
|
38 | ||||
|
39 | class HooksHttpHandler(BaseHTTPRequestHandler): | |||
|
40 | ||||
|
41 | JSON_HOOKS_PROTO = 'json.v1' | |||
|
42 | MSGPACK_HOOKS_PROTO = 'msgpack.v1' | |||
|
43 | # starting with RhodeCode 5.0.0 MsgPack is the default, prior it used json | |||
|
44 | DEFAULT_HOOKS_PROTO = MSGPACK_HOOKS_PROTO | |||
|
45 | ||||
|
46 | @classmethod | |||
|
47 | def serialize_data(cls, data, proto=DEFAULT_HOOKS_PROTO): | |||
|
48 | if proto == cls.MSGPACK_HOOKS_PROTO: | |||
|
49 | return msgpack.packb(data) | |||
|
50 | return json.dumps(data) | |||
|
51 | ||||
|
52 | @classmethod | |||
|
53 | def deserialize_data(cls, data, proto=DEFAULT_HOOKS_PROTO): | |||
|
54 | if proto == cls.MSGPACK_HOOKS_PROTO: | |||
|
55 | return msgpack.unpackb(data) | |||
|
56 | return json.loads(data) | |||
|
57 | ||||
|
58 | def do_POST(self): | |||
|
59 | hooks_proto, method, extras = self._read_request() | |||
|
60 | log.debug('Handling HooksHttpHandler %s with %s proto', method, hooks_proto) | |||
|
61 | ||||
|
62 | txn_id = getattr(self.server, 'txn_id', None) | |||
|
63 | if txn_id: | |||
|
64 | log.debug('Computing TXN_ID based on `%s`:`%s`', | |||
|
65 | extras['repository'], extras['txn_id']) | |||
|
66 | computed_txn_id = rc_cache.utils.compute_key_from_params( | |||
|
67 | extras['repository'], extras['txn_id']) | |||
|
68 | if txn_id != computed_txn_id: | |||
|
69 | raise Exception( | |||
|
70 | 'TXN ID fail: expected {} got {} instead'.format( | |||
|
71 | txn_id, computed_txn_id)) | |||
|
72 | ||||
|
73 | request = getattr(self.server, 'request', None) | |||
|
74 | try: | |||
|
75 | hooks = Hooks(request=request, log_prefix='HOOKS: {} '.format(self.server.server_address)) | |||
|
76 | result = self._call_hook_method(hooks, method, extras) | |||
|
77 | ||||
|
78 | except Exception as e: | |||
|
79 | exc_tb = traceback.format_exc() | |||
|
80 | result = { | |||
|
81 | 'exception': e.__class__.__name__, | |||
|
82 | 'exception_traceback': exc_tb, | |||
|
83 | 'exception_args': e.args | |||
|
84 | } | |||
|
85 | self._write_response(hooks_proto, result) | |||
|
86 | ||||
|
87 | def _read_request(self): | |||
|
88 | length = int(self.headers['Content-Length']) | |||
|
89 | # respect sent headers, fallback to OLD proto for compatability | |||
|
90 | hooks_proto = self.headers.get('rc-hooks-protocol') or self.JSON_HOOKS_PROTO | |||
|
91 | if hooks_proto == self.MSGPACK_HOOKS_PROTO: | |||
|
92 | # support for new vcsserver msgpack based protocol hooks | |||
|
93 | body = self.rfile.read(length) | |||
|
94 | data = self.deserialize_data(body) | |||
|
95 | else: | |||
|
96 | body = self.rfile.read(length) | |||
|
97 | data = self.deserialize_data(body) | |||
|
98 | ||||
|
99 | return hooks_proto, data['method'], data['extras'] | |||
|
100 | ||||
|
101 | def _write_response(self, hooks_proto, result): | |||
|
102 | self.send_response(200) | |||
|
103 | if hooks_proto == self.MSGPACK_HOOKS_PROTO: | |||
|
104 | self.send_header("Content-type", "application/msgpack") | |||
|
105 | self.end_headers() | |||
|
106 | data = self.serialize_data(result) | |||
|
107 | self.wfile.write(data) | |||
|
108 | else: | |||
|
109 | self.send_header("Content-type", "text/json") | |||
|
110 | self.end_headers() | |||
|
111 | data = self.serialize_data(result) | |||
|
112 | self.wfile.write(data) | |||
|
113 | ||||
|
114 | def _call_hook_method(self, hooks, method, extras): | |||
|
115 | try: | |||
|
116 | result = getattr(hooks, method)(extras) | |||
|
117 | finally: | |||
|
118 | meta.Session.remove() | |||
|
119 | return result | |||
|
120 | ||||
|
121 | def log_message(self, format, *args): | |||
|
122 | """ | |||
|
123 | This is an overridden method of BaseHTTPRequestHandler which logs using | |||
|
124 | a logging library instead of writing directly to stderr. | |||
|
125 | """ | |||
|
126 | ||||
|
127 | message = format % args | |||
|
128 | ||||
|
129 | log.debug( | |||
|
130 | "HOOKS: client=%s - - [%s] %s", self.client_address, | |||
|
131 | self.log_date_time_string(), message) | |||
|
132 | ||||
|
133 | ||||
|
134 | class ThreadedHookCallbackDaemon(object): | |||
|
135 | ||||
|
136 | _callback_thread = None | |||
|
137 | _daemon = None | |||
|
138 | _done = False | |||
|
139 | use_gevent = False | |||
|
140 | ||||
|
141 | def __init__(self, txn_id=None, host=None, port=None): | |||
|
142 | self._prepare(txn_id=txn_id, host=host, port=port) | |||
|
143 | if self.use_gevent: | |||
|
144 | self._run_func = self._run_gevent | |||
|
145 | self._stop_func = self._stop_gevent | |||
|
146 | else: | |||
|
147 | self._run_func = self._run | |||
|
148 | self._stop_func = self._stop | |||
|
149 | ||||
|
150 | def __enter__(self): | |||
|
151 | log.debug('Running `%s` callback daemon', self.__class__.__name__) | |||
|
152 | self._run_func() | |||
|
153 | return self | |||
|
154 | ||||
|
155 | def __exit__(self, exc_type, exc_val, exc_tb): | |||
|
156 | log.debug('Exiting `%s` callback daemon', self.__class__.__name__) | |||
|
157 | self._stop_func() | |||
|
158 | ||||
|
159 | def _prepare(self, txn_id=None, host=None, port=None): | |||
|
160 | raise NotImplementedError() | |||
|
161 | ||||
|
162 | def _run(self): | |||
|
163 | raise NotImplementedError() | |||
|
164 | ||||
|
165 | def _stop(self): | |||
|
166 | raise NotImplementedError() | |||
|
167 | ||||
|
168 | def _run_gevent(self): | |||
|
169 | raise NotImplementedError() | |||
|
170 | ||||
|
171 | def _stop_gevent(self): | |||
|
172 | raise NotImplementedError() | |||
|
173 | ||||
|
174 | ||||
|
175 | class HttpHooksCallbackDaemon(ThreadedHookCallbackDaemon): | |||
|
176 | """ | |||
|
177 | Context manager which will run a callback daemon in a background thread. | |||
|
178 | """ | |||
|
179 | ||||
|
180 | hooks_uri = None | |||
|
181 | ||||
|
182 | # From Python docs: Polling reduces our responsiveness to a shutdown | |||
|
183 | # request and wastes cpu at all other times. | |||
|
184 | POLL_INTERVAL = 0.01 | |||
|
185 | ||||
|
186 | use_gevent = False | |||
|
187 | ||||
|
188 | @property | |||
|
189 | def _hook_prefix(self): | |||
|
190 | return 'HOOKS: {} '.format(self.hooks_uri) | |||
|
191 | ||||
|
192 | def get_hostname(self): | |||
|
193 | return socket.gethostname() or '127.0.0.1' | |||
|
194 | ||||
|
195 | def get_available_port(self, min_port=20000, max_port=65535): | |||
|
196 | from rhodecode.lib.utils2 import get_available_port as _get_port | |||
|
197 | return _get_port(min_port, max_port) | |||
|
198 | ||||
|
199 | def _prepare(self, txn_id=None, host=None, port=None): | |||
|
200 | from pyramid.threadlocal import get_current_request | |||
|
201 | ||||
|
202 | if not host or host == "*": | |||
|
203 | host = self.get_hostname() | |||
|
204 | if not port: | |||
|
205 | port = self.get_available_port() | |||
|
206 | ||||
|
207 | server_address = (host, port) | |||
|
208 | self.hooks_uri = '{}:{}'.format(host, port) | |||
|
209 | self.txn_id = txn_id | |||
|
210 | self._done = False | |||
|
211 | ||||
|
212 | log.debug( | |||
|
213 | "%s Preparing HTTP callback daemon registering hook object: %s", | |||
|
214 | self._hook_prefix, HooksHttpHandler) | |||
|
215 | ||||
|
216 | self._daemon = TCPServer(server_address, HooksHttpHandler) | |||
|
217 | # inject transaction_id for later verification | |||
|
218 | self._daemon.txn_id = self.txn_id | |||
|
219 | ||||
|
220 | # pass the WEB app request into daemon | |||
|
221 | self._daemon.request = get_current_request() | |||
|
222 | ||||
|
223 | def _run(self): | |||
|
224 | log.debug("Running thread-based loop of callback daemon in background") | |||
|
225 | callback_thread = threading.Thread( | |||
|
226 | target=self._daemon.serve_forever, | |||
|
227 | kwargs={'poll_interval': self.POLL_INTERVAL}) | |||
|
228 | callback_thread.daemon = True | |||
|
229 | callback_thread.start() | |||
|
230 | self._callback_thread = callback_thread | |||
|
231 | ||||
|
232 | def _run_gevent(self): | |||
|
233 | log.debug("Running gevent-based loop of callback daemon in background") | |||
|
234 | # create a new greenlet for the daemon's serve_forever method | |||
|
235 | callback_greenlet = gevent.spawn( | |||
|
236 | self._daemon.serve_forever, | |||
|
237 | poll_interval=self.POLL_INTERVAL) | |||
|
238 | ||||
|
239 | # store reference to greenlet | |||
|
240 | self._callback_greenlet = callback_greenlet | |||
|
241 | ||||
|
242 | # switch to this greenlet | |||
|
243 | gevent.sleep(0.01) | |||
|
244 | ||||
|
245 | def _stop(self): | |||
|
246 | log.debug("Waiting for background thread to finish.") | |||
|
247 | self._daemon.shutdown() | |||
|
248 | self._callback_thread.join() | |||
|
249 | self._daemon = None | |||
|
250 | self._callback_thread = None | |||
|
251 | if self.txn_id: | |||
|
252 | txn_id_file = get_txn_id_data_path(self.txn_id) | |||
|
253 | log.debug('Cleaning up TXN ID %s', txn_id_file) | |||
|
254 | if os.path.isfile(txn_id_file): | |||
|
255 | os.remove(txn_id_file) | |||
|
256 | ||||
|
257 | log.debug("Background thread done.") | |||
|
258 | ||||
|
259 | def _stop_gevent(self): | |||
|
260 | log.debug("Waiting for background greenlet to finish.") | |||
|
261 | ||||
|
262 | # if greenlet exists and is running | |||
|
263 | if self._callback_greenlet and not self._callback_greenlet.dead: | |||
|
264 | # shutdown daemon if it exists | |||
|
265 | if self._daemon: | |||
|
266 | self._daemon.shutdown() | |||
|
267 | ||||
|
268 | # kill the greenlet | |||
|
269 | self._callback_greenlet.kill() | |||
|
270 | ||||
|
271 | self._daemon = None | |||
|
272 | self._callback_greenlet = None | |||
|
273 | ||||
|
274 | if self.txn_id: | |||
|
275 | txn_id_file = get_txn_id_data_path(self.txn_id) | |||
|
276 | log.debug('Cleaning up TXN ID %s', txn_id_file) | |||
|
277 | if os.path.isfile(txn_id_file): | |||
|
278 | os.remove(txn_id_file) | |||
|
279 | ||||
|
280 | log.debug("Background greenlet done.") |
@@ -20,12 +20,11 b' import os' | |||||
20 | import re |
|
20 | import re | |
21 | import logging |
|
21 | import logging | |
22 | import datetime |
|
22 | import datetime | |
23 | import configparser |
|
|||
24 | from sqlalchemy import Table |
|
23 | from sqlalchemy import Table | |
25 |
|
24 | |||
26 | from rhodecode.lib.utils import call_service_api |
|
25 | from rhodecode.lib.api_utils import call_service_api | |
27 | from rhodecode.lib.utils2 import AttributeDict |
|
26 | from rhodecode.lib.utils2 import AttributeDict | |
28 | from rhodecode.model.scm import ScmModel |
|
27 | from rhodecode.lib.vcs.exceptions import ImproperlyConfiguredError | |
29 |
|
28 | |||
30 | from .hg import MercurialServer |
|
29 | from .hg import MercurialServer | |
31 | from .git import GitServer |
|
30 | from .git import GitServer | |
@@ -39,7 +38,7 b' class SshWrapper(object):' | |||||
39 | svn_cmd_pat = re.compile(r'^svnserve -t') |
|
38 | svn_cmd_pat = re.compile(r'^svnserve -t') | |
40 |
|
39 | |||
41 | def __init__(self, command, connection_info, mode, |
|
40 | def __init__(self, command, connection_info, mode, | |
42 | user, user_id, key_id: int, shell, ini_path: str, env): |
|
41 | user, user_id, key_id: int, shell, ini_path: str, settings, env): | |
43 | self.command = command |
|
42 | self.command = command | |
44 | self.connection_info = connection_info |
|
43 | self.connection_info = connection_info | |
45 | self.mode = mode |
|
44 | self.mode = mode | |
@@ -49,15 +48,9 b' class SshWrapper(object):' | |||||
49 | self.shell = shell |
|
48 | self.shell = shell | |
50 | self.ini_path = ini_path |
|
49 | self.ini_path = ini_path | |
51 | self.env = env |
|
50 | self.env = env | |
52 |
|
51 | self.settings = settings | ||
53 | self.config = self.parse_config(ini_path) |
|
|||
54 | self.server_impl = None |
|
52 | self.server_impl = None | |
55 |
|
53 | |||
56 | def parse_config(self, config_path): |
|
|||
57 | parser = configparser.ConfigParser() |
|
|||
58 | parser.read(config_path) |
|
|||
59 | return parser |
|
|||
60 |
|
||||
61 | def update_key_access_time(self, key_id): |
|
54 | def update_key_access_time(self, key_id): | |
62 | from rhodecode.model.meta import raw_query_executor, Base |
|
55 | from rhodecode.model.meta import raw_query_executor, Base | |
63 |
|
56 | |||
@@ -162,6 +155,9 b' class SshWrapper(object):' | |||||
162 | return vcs_type, repo_name, mode |
|
155 | return vcs_type, repo_name, mode | |
163 |
|
156 | |||
164 | def serve(self, vcs, repo, mode, user, permissions, branch_permissions): |
|
157 | def serve(self, vcs, repo, mode, user, permissions, branch_permissions): | |
|
158 | # TODO: remove this once we have .ini defined access path... | |||
|
159 | from rhodecode.model.scm import ScmModel | |||
|
160 | ||||
165 | store = ScmModel().repos_path |
|
161 | store = ScmModel().repos_path | |
166 |
|
162 | |||
167 | check_branch_perms = False |
|
163 | check_branch_perms = False | |
@@ -186,7 +182,7 b' class SshWrapper(object):' | |||||
186 | server = MercurialServer( |
|
182 | server = MercurialServer( | |
187 | store=store, ini_path=self.ini_path, |
|
183 | store=store, ini_path=self.ini_path, | |
188 | repo_name=repo, user=user, |
|
184 | repo_name=repo, user=user, | |
189 |
user_permissions=permissions, |
|
185 | user_permissions=permissions, settings=self.settings, env=self.env) | |
190 | self.server_impl = server |
|
186 | self.server_impl = server | |
191 | return server.run(tunnel_extras=extras) |
|
187 | return server.run(tunnel_extras=extras) | |
192 |
|
188 | |||
@@ -194,7 +190,7 b' class SshWrapper(object):' | |||||
194 | server = GitServer( |
|
190 | server = GitServer( | |
195 | store=store, ini_path=self.ini_path, |
|
191 | store=store, ini_path=self.ini_path, | |
196 | repo_name=repo, repo_mode=mode, user=user, |
|
192 | repo_name=repo, repo_mode=mode, user=user, | |
197 |
user_permissions=permissions, |
|
193 | user_permissions=permissions, settings=self.settings, env=self.env) | |
198 | self.server_impl = server |
|
194 | self.server_impl = server | |
199 | return server.run(tunnel_extras=extras) |
|
195 | return server.run(tunnel_extras=extras) | |
200 |
|
196 | |||
@@ -202,7 +198,7 b' class SshWrapper(object):' | |||||
202 | server = SubversionServer( |
|
198 | server = SubversionServer( | |
203 | store=store, ini_path=self.ini_path, |
|
199 | store=store, ini_path=self.ini_path, | |
204 | repo_name=None, user=user, |
|
200 | repo_name=None, user=user, | |
205 |
user_permissions=permissions, |
|
201 | user_permissions=permissions, settings=self.settings, env=self.env) | |
206 | self.server_impl = server |
|
202 | self.server_impl = server | |
207 | return server.run(tunnel_extras=extras) |
|
203 | return server.run(tunnel_extras=extras) | |
208 |
|
204 | |||
@@ -269,6 +265,35 b' class SshWrapperStandalone(SshWrapper):' | |||||
269 | New version of SshWrapper designed to be depended only on service API |
|
265 | New version of SshWrapper designed to be depended only on service API | |
270 | """ |
|
266 | """ | |
271 | repos_path = None |
|
267 | repos_path = None | |
|
268 | service_api_host: str | |||
|
269 | service_api_token: str | |||
|
270 | api_url: str | |||
|
271 | ||||
|
272 | def __init__(self, command, connection_info, mode, | |||
|
273 | user, user_id, key_id: int, shell, ini_path: str, settings, env): | |||
|
274 | ||||
|
275 | # validate our settings for making a standalone calls | |||
|
276 | try: | |||
|
277 | self.service_api_host = settings['app.service_api.host'] | |||
|
278 | self.service_api_token = settings['app.service_api.token'] | |||
|
279 | except KeyError: | |||
|
280 | raise ImproperlyConfiguredError( | |||
|
281 | "app.service_api.host or app.service_api.token are missing. " | |||
|
282 | "Please ensure that app.service_api.host and app.service_api.token are " | |||
|
283 | "defined inside of .ini configuration file." | |||
|
284 | ) | |||
|
285 | ||||
|
286 | try: | |||
|
287 | self.api_url = settings['rhodecode.api.url'] | |||
|
288 | except KeyError: | |||
|
289 | raise ImproperlyConfiguredError( | |||
|
290 | "rhodecode.api.url is missing. " | |||
|
291 | "Please ensure that rhodecode.api.url is " | |||
|
292 | "defined inside of .ini configuration file." | |||
|
293 | ) | |||
|
294 | ||||
|
295 | super(SshWrapperStandalone, self).__init__( | |||
|
296 | command, connection_info, mode, user, user_id, key_id, shell, ini_path, settings, env) | |||
272 |
|
297 | |||
273 | @staticmethod |
|
298 | @staticmethod | |
274 | def parse_user_related_data(user_data): |
|
299 | def parse_user_related_data(user_data): | |
@@ -301,7 +326,7 b' class SshWrapperStandalone(SshWrapper):' | |||||
301 | exit_code = 1 |
|
326 | exit_code = 1 | |
302 |
|
327 | |||
303 | elif scm_detected: |
|
328 | elif scm_detected: | |
304 |
data = call_service_api(self. |
|
329 | data = call_service_api(self.service_api_host, self.service_api_token, self.api_url, { | |
305 | "method": "service_get_data_for_ssh_wrapper", |
|
330 | "method": "service_get_data_for_ssh_wrapper", | |
306 | "args": {"user_id": user_id, "repo_name": scm_repo, "key_id": self.key_id} |
|
331 | "args": {"user_id": user_id, "repo_name": scm_repo, "key_id": self.key_id} | |
307 | }) |
|
332 | }) | |
@@ -339,7 +364,7 b' class SshWrapperStandalone(SshWrapper):' | |||||
339 | if repo_name.startswith('_'): |
|
364 | if repo_name.startswith('_'): | |
340 | org_repo_name = repo_name |
|
365 | org_repo_name = repo_name | |
341 | log.debug('translating UID repo %s', org_repo_name) |
|
366 | log.debug('translating UID repo %s', org_repo_name) | |
342 |
by_id_match = call_service_api(self. |
|
367 | by_id_match = call_service_api(self.service_api_host, self.service_api_token, self.api_url, { | |
343 | 'method': 'service_get_repo_name_by_id', |
|
368 | 'method': 'service_get_repo_name_by_id', | |
344 | "args": {"repo_id": repo_name} |
|
369 | "args": {"repo_id": repo_name} | |
345 | }) |
|
370 | }) | |
@@ -375,17 +400,17 b' class SshWrapperStandalone(SshWrapper):' | |||||
375 | server = MercurialServer( |
|
400 | server = MercurialServer( | |
376 | store=store, ini_path=self.ini_path, |
|
401 | store=store, ini_path=self.ini_path, | |
377 | repo_name=repo, user=user, |
|
402 | repo_name=repo, user=user, | |
378 |
user_permissions=permissions, |
|
403 | user_permissions=permissions, settings=self.settings, env=self.env) | |
379 | case 'git': |
|
404 | case 'git': | |
380 | server = GitServer( |
|
405 | server = GitServer( | |
381 | store=store, ini_path=self.ini_path, |
|
406 | store=store, ini_path=self.ini_path, | |
382 | repo_name=repo, repo_mode=mode, user=user, |
|
407 | repo_name=repo, repo_mode=mode, user=user, | |
383 |
user_permissions=permissions, |
|
408 | user_permissions=permissions, settings=self.settings, env=self.env) | |
384 | case 'svn': |
|
409 | case 'svn': | |
385 | server = SubversionServer( |
|
410 | server = SubversionServer( | |
386 | store=store, ini_path=self.ini_path, |
|
411 | store=store, ini_path=self.ini_path, | |
387 | repo_name=None, user=user, |
|
412 | repo_name=None, user=user, | |
388 |
user_permissions=permissions, |
|
413 | user_permissions=permissions, settings=self.settings, env=self.env) | |
389 | case _: |
|
414 | case _: | |
390 | raise Exception(f'Unrecognised VCS: {vcs}') |
|
415 | raise Exception(f'Unrecognised VCS: {vcs}') | |
391 | self.server_impl = server |
|
416 | self.server_impl = server |
@@ -20,27 +20,27 b' import os' | |||||
20 | import sys |
|
20 | import sys | |
21 | import logging |
|
21 | import logging | |
22 |
|
22 | |||
23 |
from rhodecode.lib.hook |
|
23 | from rhodecode.lib.hook_daemon.base import prepare_callback_daemon | |
24 | from rhodecode.lib.ext_json import sjson as json |
|
24 | from rhodecode.lib.ext_json import sjson as json | |
25 | from rhodecode.lib.vcs.conf import settings as vcs_settings |
|
25 | from rhodecode.lib.vcs.conf import settings as vcs_settings | |
26 | from rhodecode.lib.utils import call_service_api |
|
26 | from rhodecode.lib.api_utils import call_service_api | |
27 | from rhodecode.model.scm import ScmModel |
|
|||
28 |
|
27 | |||
29 | log = logging.getLogger(__name__) |
|
28 | log = logging.getLogger(__name__) | |
30 |
|
29 | |||
31 |
|
30 | |||
32 | class VcsServer(object): |
|
31 | class SSHVcsServer(object): | |
33 | repo_user_agent = None # set in child classes |
|
32 | repo_user_agent = None # set in child classes | |
34 | _path = None # set executable path for hg/git/svn binary |
|
33 | _path = None # set executable path for hg/git/svn binary | |
35 | backend = None # set in child classes |
|
34 | backend = None # set in child classes | |
36 | tunnel = None # subprocess handling tunnel |
|
35 | tunnel = None # subprocess handling tunnel | |
|
36 | settings = None # parsed settings module | |||
37 | write_perms = ['repository.admin', 'repository.write'] |
|
37 | write_perms = ['repository.admin', 'repository.write'] | |
38 | read_perms = ['repository.read', 'repository.admin', 'repository.write'] |
|
38 | read_perms = ['repository.read', 'repository.admin', 'repository.write'] | |
39 |
|
39 | |||
40 |
def __init__(self, user, user_permissions, |
|
40 | def __init__(self, user, user_permissions, settings, env): | |
41 | self.user = user |
|
41 | self.user = user | |
42 | self.user_permissions = user_permissions |
|
42 | self.user_permissions = user_permissions | |
43 |
self. |
|
43 | self.settings = settings | |
44 | self.env = env |
|
44 | self.env = env | |
45 | self.stdin = sys.stdin |
|
45 | self.stdin = sys.stdin | |
46 |
|
46 | |||
@@ -59,9 +59,14 b' class VcsServer(object):' | |||||
59 | # Todo: Leave only "celery" case after transition. |
|
59 | # Todo: Leave only "celery" case after transition. | |
60 | match self.hooks_protocol: |
|
60 | match self.hooks_protocol: | |
61 | case 'http': |
|
61 | case 'http': | |
|
62 | from rhodecode.model.scm import ScmModel | |||
62 | ScmModel().mark_for_invalidation(repo_name) |
|
63 | ScmModel().mark_for_invalidation(repo_name) | |
63 | case 'celery': |
|
64 | case 'celery': | |
64 |
|
|
65 | service_api_host = self.settings['app.service_api.host'] | |
|
66 | service_api_token = self.settings['app.service_api.token'] | |||
|
67 | api_url = self.settings['rhodecode.api.url'] | |||
|
68 | ||||
|
69 | call_service_api(service_api_host, service_api_token, api_url, { | |||
65 | "method": "service_mark_for_invalidation", |
|
70 | "method": "service_mark_for_invalidation", | |
66 | "args": {"repo_name": repo_name} |
|
71 | "args": {"repo_name": repo_name} | |
67 | }) |
|
72 | }) | |
@@ -118,7 +123,7 b' class VcsServer(object):' | |||||
118 | 'server_url': None, |
|
123 | 'server_url': None, | |
119 | 'user_agent': f'{self.repo_user_agent}/ssh-user-agent', |
|
124 | 'user_agent': f'{self.repo_user_agent}/ssh-user-agent', | |
120 | 'hooks': ['push', 'pull'], |
|
125 | 'hooks': ['push', 'pull'], | |
121 |
'hooks_module': 'rhodecode.lib.hook |
|
126 | 'hooks_module': 'rhodecode.lib.hook_daemon.hook_module', | |
122 | 'is_shadow_repo': False, |
|
127 | 'is_shadow_repo': False, | |
123 | 'detect_force_push': False, |
|
128 | 'detect_force_push': False, | |
124 | 'check_branch_perms': False, |
|
129 | 'check_branch_perms': False, | |
@@ -156,7 +161,7 b' class VcsServer(object):' | |||||
156 | return exit_code, action == "push" |
|
161 | return exit_code, action == "push" | |
157 |
|
162 | |||
158 | def run(self, tunnel_extras=None): |
|
163 | def run(self, tunnel_extras=None): | |
159 |
self.hooks_protocol = self. |
|
164 | self.hooks_protocol = self.settings['vcs.hooks.protocol'] | |
160 | tunnel_extras = tunnel_extras or {} |
|
165 | tunnel_extras = tunnel_extras or {} | |
161 | extras = {} |
|
166 | extras = {} | |
162 | extras.update(tunnel_extras) |
|
167 | extras.update(tunnel_extras) |
@@ -21,7 +21,7 b' import logging' | |||||
21 | import subprocess |
|
21 | import subprocess | |
22 |
|
22 | |||
23 | from vcsserver import hooks |
|
23 | from vcsserver import hooks | |
24 | from .base import VcsServer |
|
24 | from .base import SSHVcsServer | |
25 |
|
25 | |||
26 | log = logging.getLogger(__name__) |
|
26 | log = logging.getLogger(__name__) | |
27 |
|
27 | |||
@@ -70,19 +70,17 b' class GitTunnelWrapper(object):' | |||||
70 | return result |
|
70 | return result | |
71 |
|
71 | |||
72 |
|
72 | |||
73 | class GitServer(VcsServer): |
|
73 | class GitServer(SSHVcsServer): | |
74 | backend = 'git' |
|
74 | backend = 'git' | |
75 | repo_user_agent = 'git' |
|
75 | repo_user_agent = 'git' | |
76 |
|
76 | |||
77 | def __init__(self, store, ini_path, repo_name, repo_mode, |
|
77 | def __init__(self, store, ini_path, repo_name, repo_mode, user, user_permissions, settings, env): | |
78 |
|
|
78 | super().__init__(user, user_permissions, settings, env) | |
79 | super().\ |
|
|||
80 | __init__(user, user_permissions, config, env) |
|
|||
81 |
|
79 | |||
82 | self.store = store |
|
80 | self.store = store | |
83 | self.ini_path = ini_path |
|
81 | self.ini_path = ini_path | |
84 | self.repo_name = repo_name |
|
82 | self.repo_name = repo_name | |
85 |
self._path = self.git_path = |
|
83 | self._path = self.git_path = settings['ssh.executable.git'] | |
86 |
|
84 | |||
87 | self.repo_mode = repo_mode |
|
85 | self.repo_mode = repo_mode | |
88 | self.tunnel = GitTunnelWrapper(server=self) |
|
86 | self.tunnel = GitTunnelWrapper(server=self) |
@@ -22,10 +22,10 b' import logging' | |||||
22 | import tempfile |
|
22 | import tempfile | |
23 | import textwrap |
|
23 | import textwrap | |
24 | import collections |
|
24 | import collections | |
25 | from .base import VcsServer |
|
25 | ||
26 | from rhodecode.lib.utils import call_service_api |
|
26 | from .base import SSHVcsServer | |
27 | from rhodecode.model.db import RhodeCodeUi |
|
27 | ||
28 |
from rhodecode. |
|
28 | from rhodecode.lib.api_utils import call_service_api | |
29 |
|
29 | |||
30 | log = logging.getLogger(__name__) |
|
30 | log = logging.getLogger(__name__) | |
31 |
|
31 | |||
@@ -57,7 +57,7 b' class MercurialTunnelWrapper(object):' | |||||
57 | # cleanup custom hgrc file |
|
57 | # cleanup custom hgrc file | |
58 | if os.path.isfile(hgrc_custom): |
|
58 | if os.path.isfile(hgrc_custom): | |
59 | with open(hgrc_custom, 'wb') as f: |
|
59 | with open(hgrc_custom, 'wb') as f: | |
60 | f.write('') |
|
60 | f.write(b'') | |
61 | log.debug('Cleanup custom hgrc file under %s', hgrc_custom) |
|
61 | log.debug('Cleanup custom hgrc file under %s', hgrc_custom) | |
62 |
|
62 | |||
63 | # write temp |
|
63 | # write temp | |
@@ -94,62 +94,67 b' class MercurialTunnelWrapper(object):' | |||||
94 | self.remove_configs() |
|
94 | self.remove_configs() | |
95 |
|
95 | |||
96 |
|
96 | |||
97 | class MercurialServer(VcsServer): |
|
97 | class MercurialServer(SSHVcsServer): | |
98 | backend = 'hg' |
|
98 | backend = 'hg' | |
99 | repo_user_agent = 'mercurial' |
|
99 | repo_user_agent = 'mercurial' | |
100 | cli_flags = ['phases', 'largefiles', 'extensions', 'experimental', 'hooks'] |
|
100 | cli_flags = ['phases', 'largefiles', 'extensions', 'experimental', 'hooks'] | |
101 |
|
101 | |||
102 |
def __init__(self, store, ini_path, repo_name, user, user_permissions, |
|
102 | def __init__(self, store, ini_path, repo_name, user, user_permissions, settings, env): | |
103 |
super().__init__(user, user_permissions, |
|
103 | super().__init__(user, user_permissions, settings, env) | |
104 |
|
104 | |||
105 | self.store = store |
|
105 | self.store = store | |
106 | self.ini_path = ini_path |
|
106 | self.ini_path = ini_path | |
107 | self.repo_name = repo_name |
|
107 | self.repo_name = repo_name | |
108 |
self._path = self.hg_path = |
|
108 | self._path = self.hg_path = settings['ssh.executable.hg'] | |
109 | self.tunnel = MercurialTunnelWrapper(server=self) |
|
109 | self.tunnel = MercurialTunnelWrapper(server=self) | |
110 |
|
110 | |||
111 | def config_to_hgrc(self, repo_name): |
|
111 | def config_to_hgrc(self, repo_name): | |
112 | # Todo: once transition is done only call to service api should exist |
|
112 | # Todo: once transition is done only call to service api should exist | |
113 | if self.hooks_protocol == 'celery': |
|
113 | if self.hooks_protocol == 'celery': | |
114 | data = call_service_api(self.ini_path, { |
|
114 | service_api_host = self.settings['app.service_api.host'] | |
|
115 | service_api_token = self.settings['app.service_api.token'] | |||
|
116 | api_url = self.settings['rhodecode.api.url'] | |||
|
117 | data = call_service_api(service_api_host, service_api_token, api_url, { | |||
115 | "method": "service_config_to_hgrc", |
|
118 | "method": "service_config_to_hgrc", | |
116 | "args": {"cli_flags": self.cli_flags, "repo_name": repo_name} |
|
119 | "args": {"cli_flags": self.cli_flags, "repo_name": repo_name} | |
117 | }) |
|
120 | }) | |
118 | return data['flags'] |
|
121 | return data['flags'] | |
119 |
|
122 | else: | ||
120 | ui_sections = collections.defaultdict(list) |
|
123 | from rhodecode.model.db import RhodeCodeUi | |
121 | ui = VcsSettingsModel(repo=repo_name).get_ui_settings(section=None, key=None) |
|
124 | from rhodecode.model.settings import VcsSettingsModel | |
|
125 | ui_sections = collections.defaultdict(list) | |||
|
126 | ui = VcsSettingsModel(repo=repo_name).get_ui_settings(section=None, key=None) | |||
122 |
|
127 | |||
123 | # write default hooks |
|
128 | # write default hooks | |
124 | default_hooks = [ |
|
129 | default_hooks = [ | |
125 | ('pretxnchangegroup.ssh_auth', 'python:vcsserver.hooks.pre_push_ssh_auth'), |
|
130 | ('pretxnchangegroup.ssh_auth', 'python:vcsserver.hooks.pre_push_ssh_auth'), | |
126 | ('pretxnchangegroup.ssh', 'python:vcsserver.hooks.pre_push_ssh'), |
|
131 | ('pretxnchangegroup.ssh', 'python:vcsserver.hooks.pre_push_ssh'), | |
127 | ('changegroup.ssh', 'python:vcsserver.hooks.post_push_ssh'), |
|
132 | ('changegroup.ssh', 'python:vcsserver.hooks.post_push_ssh'), | |
128 |
|
133 | |||
129 | ('preoutgoing.ssh', 'python:vcsserver.hooks.pre_pull_ssh'), |
|
134 | ('preoutgoing.ssh', 'python:vcsserver.hooks.pre_pull_ssh'), | |
130 | ('outgoing.ssh', 'python:vcsserver.hooks.post_pull_ssh'), |
|
135 | ('outgoing.ssh', 'python:vcsserver.hooks.post_pull_ssh'), | |
131 | ] |
|
136 | ] | |
132 |
|
137 | |||
133 | for k, v in default_hooks: |
|
138 | for k, v in default_hooks: | |
134 | ui_sections['hooks'].append((k, v)) |
|
139 | ui_sections['hooks'].append((k, v)) | |
135 |
|
140 | |||
136 | for entry in ui: |
|
141 | for entry in ui: | |
137 | if not entry.active: |
|
142 | if not entry.active: | |
138 | continue |
|
|||
139 | sec = entry.section |
|
|||
140 | key = entry.key |
|
|||
141 |
|
||||
142 | if sec in self.cli_flags: |
|
|||
143 | # we want only custom hooks, so we skip builtins |
|
|||
144 | if sec == 'hooks' and key in RhodeCodeUi.HOOKS_BUILTIN: |
|
|||
145 | continue |
|
143 | continue | |
|
144 | sec = entry.section | |||
|
145 | key = entry.key | |||
146 |
|
146 | |||
147 | ui_sections[sec].append([key, entry.value]) |
|
147 | if sec in self.cli_flags: | |
|
148 | # we want only custom hooks, so we skip builtins | |||
|
149 | if sec == 'hooks' and key in RhodeCodeUi.HOOKS_BUILTIN: | |||
|
150 | continue | |||
148 |
|
151 | |||
149 | flags = [] |
|
152 | ui_sections[sec].append([key, entry.value]) | |
150 | for _sec, key_val in ui_sections.items(): |
|
153 | ||
151 |
flags |
|
154 | flags = [] | |
152 | flags.append(f'[{_sec}]') |
|
155 | for _sec, key_val in ui_sections.items(): | |
153 | for key, val in key_val: |
|
156 | flags.append(' ') | |
154 |
flags.append(f'{ |
|
157 | flags.append(f'[{_sec}]') | |
155 | return flags |
|
158 | for key, val in key_val: | |
|
159 | flags.append(f'{key}= {val}') | |||
|
160 | return flags |
@@ -25,7 +25,7 b' import tempfile' | |||||
25 | from subprocess import Popen, PIPE |
|
25 | from subprocess import Popen, PIPE | |
26 | import urllib.parse |
|
26 | import urllib.parse | |
27 |
|
27 | |||
28 | from .base import VcsServer |
|
28 | from .base import SSHVcsServer | |
29 |
|
29 | |||
30 | log = logging.getLogger(__name__) |
|
30 | log = logging.getLogger(__name__) | |
31 |
|
31 | |||
@@ -218,20 +218,18 b' class SubversionTunnelWrapper(object):' | |||||
218 | return self.return_code |
|
218 | return self.return_code | |
219 |
|
219 | |||
220 |
|
220 | |||
221 | class SubversionServer(VcsServer): |
|
221 | class SubversionServer(SSHVcsServer): | |
222 | backend = 'svn' |
|
222 | backend = 'svn' | |
223 | repo_user_agent = 'svn' |
|
223 | repo_user_agent = 'svn' | |
224 |
|
224 | |||
225 | def __init__(self, store, ini_path, repo_name, |
|
225 | def __init__(self, store, ini_path, repo_name, user, user_permissions, settings, env): | |
226 |
|
|
226 | super().__init__(user, user_permissions, settings, env) | |
227 | super()\ |
|
|||
228 | .__init__(user, user_permissions, config, env) |
|
|||
229 | self.store = store |
|
227 | self.store = store | |
230 | self.ini_path = ini_path |
|
228 | self.ini_path = ini_path | |
231 | # NOTE(dan): repo_name at this point is empty, |
|
229 | # NOTE(dan): repo_name at this point is empty, | |
232 | # this is set later in .run() based from parsed input stream |
|
230 | # this is set later in .run() based from parsed input stream | |
233 | self.repo_name = repo_name |
|
231 | self.repo_name = repo_name | |
234 |
self._path = self.svn_path = |
|
232 | self._path = self.svn_path = settings['ssh.executable.svn'] | |
235 |
|
233 | |||
236 | self.tunnel = SubversionTunnelWrapper(server=self) |
|
234 | self.tunnel = SubversionTunnelWrapper(server=self) | |
237 |
|
235 |
@@ -31,7 +31,6 b' from .utils import setup_custom_logging' | |||||
31 | log = logging.getLogger(__name__) |
|
31 | log = logging.getLogger(__name__) | |
32 |
|
32 | |||
33 |
|
33 | |||
34 |
|
||||
35 | @click.command() |
|
34 | @click.command() | |
36 | @click.argument('ini_path', type=click.Path(exists=True)) |
|
35 | @click.argument('ini_path', type=click.Path(exists=True)) | |
37 | @click.option( |
|
36 | @click.option( | |
@@ -55,11 +54,12 b' def main(ini_path, mode, user, user_id, ' | |||||
55 | connection_info = os.environ.get('SSH_CONNECTION', '') |
|
54 | connection_info = os.environ.get('SSH_CONNECTION', '') | |
56 | time_start = time.time() |
|
55 | time_start = time.time() | |
57 | with bootstrap(ini_path, env={'RC_CMD_SSH_WRAPPER': '1'}) as env: |
|
56 | with bootstrap(ini_path, env={'RC_CMD_SSH_WRAPPER': '1'}) as env: | |
|
57 | settings = env['registry'].settings | |||
58 | statsd = StatsdClient.statsd |
|
58 | statsd = StatsdClient.statsd | |
59 | try: |
|
59 | try: | |
60 | ssh_wrapper = SshWrapper( |
|
60 | ssh_wrapper = SshWrapper( | |
61 | command, connection_info, mode, |
|
61 | command, connection_info, mode, | |
62 | user, user_id, key_id, shell, ini_path, env) |
|
62 | user, user_id, key_id, shell, ini_path, settings, env) | |
63 | except Exception: |
|
63 | except Exception: | |
64 | log.exception('Failed to execute SshWrapper') |
|
64 | log.exception('Failed to execute SshWrapper') | |
65 | sys.exit(-5) |
|
65 | sys.exit(-5) |
@@ -16,6 +16,14 b'' | |||||
16 | # RhodeCode Enterprise Edition, including its added features, Support services, |
|
16 | # RhodeCode Enterprise Edition, including its added features, Support services, | |
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
18 |
|
18 | |||
|
19 | """ | |||
|
20 | WARNING: be really carefully with changing ANY imports in this file | |||
|
21 | # This script is to mean as really fast executable, doing some imports here that would yield an import chain change | |||
|
22 | # can affect execution times... | |||
|
23 | # This can be easily debugged using such command:: | |||
|
24 | # time PYTHONPROFILEIMPORTTIME=1 rc-ssh-wrapper-v2 --debug --mode=test .dev/dev.ini | |||
|
25 | """ | |||
|
26 | ||||
19 | import os |
|
27 | import os | |
20 | import sys |
|
28 | import sys | |
21 | import time |
|
29 | import time | |
@@ -23,9 +31,12 b' import logging' | |||||
23 |
|
31 | |||
24 | import click |
|
32 | import click | |
25 |
|
33 | |||
|
34 | from rhodecode.config.config_maker import sanitize_settings_and_apply_defaults | |||
26 | from rhodecode.lib.statsd_client import StatsdClient |
|
35 | from rhodecode.lib.statsd_client import StatsdClient | |
|
36 | from rhodecode.lib.config_utils import get_app_config_lightweight | |||
|
37 | ||||
|
38 | from .utils import setup_custom_logging | |||
27 | from .backends import SshWrapperStandalone |
|
39 | from .backends import SshWrapperStandalone | |
28 | from .utils import setup_custom_logging |
|
|||
29 |
|
40 | |||
30 | log = logging.getLogger(__name__) |
|
41 | log = logging.getLogger(__name__) | |
31 |
|
42 | |||
@@ -42,6 +53,8 b' log = logging.getLogger(__name__)' | |||||
42 | @click.option('--shell', '-s', is_flag=True, help='Allow Shell') |
|
53 | @click.option('--shell', '-s', is_flag=True, help='Allow Shell') | |
43 | @click.option('--debug', is_flag=True, help='Enabled detailed output logging') |
|
54 | @click.option('--debug', is_flag=True, help='Enabled detailed output logging') | |
44 | def main(ini_path, mode, user, user_id, key_id, shell, debug): |
|
55 | def main(ini_path, mode, user, user_id, key_id, shell, debug): | |
|
56 | ||||
|
57 | time_start = time.time() | |||
45 | setup_custom_logging(ini_path, debug) |
|
58 | setup_custom_logging(ini_path, debug) | |
46 |
|
59 | |||
47 | command = os.environ.get('SSH_ORIGINAL_COMMAND', '') |
|
60 | command = os.environ.get('SSH_ORIGINAL_COMMAND', '') | |
@@ -50,21 +63,30 b' def main(ini_path, mode, user, user_id, ' | |||||
50 | 'Unable to fetch SSH_ORIGINAL_COMMAND from environment.' |
|
63 | 'Unable to fetch SSH_ORIGINAL_COMMAND from environment.' | |
51 | 'Please make sure this is set and available during execution ' |
|
64 | 'Please make sure this is set and available during execution ' | |
52 | 'of this script.') |
|
65 | 'of this script.') | |
53 | connection_info = os.environ.get('SSH_CONNECTION', '') |
|
66 | ||
54 | time_start = time.time() |
|
67 | # initialize settings and get defaults | |
55 | env = {'RC_CMD_SSH_WRAPPER': '1'} |
|
68 | settings = get_app_config_lightweight(ini_path) | |
|
69 | settings = sanitize_settings_and_apply_defaults({'__file__': ini_path}, settings) | |||
|
70 | ||||
|
71 | # init and bootstrap StatsdClient | |||
|
72 | StatsdClient.setup(settings) | |||
56 | statsd = StatsdClient.statsd |
|
73 | statsd = StatsdClient.statsd | |
|
74 | ||||
57 | try: |
|
75 | try: | |
|
76 | connection_info = os.environ.get('SSH_CONNECTION', '') | |||
|
77 | env = {'RC_CMD_SSH_WRAPPER': '1'} | |||
58 | ssh_wrapper = SshWrapperStandalone( |
|
78 | ssh_wrapper = SshWrapperStandalone( | |
59 | command, connection_info, mode, |
|
79 | command, connection_info, mode, | |
60 | user, user_id, key_id, shell, ini_path, env) |
|
80 | user, user_id, key_id, shell, ini_path, settings, env) | |
61 | except Exception: |
|
81 | except Exception: | |
62 | log.exception('Failed to execute SshWrapper') |
|
82 | log.exception('Failed to execute SshWrapper') | |
63 | sys.exit(-5) |
|
83 | sys.exit(-5) | |
|
84 | ||||
64 | return_code = ssh_wrapper.wrap() |
|
85 | return_code = ssh_wrapper.wrap() | |
65 | operation_took = time.time() - time_start |
|
86 | operation_took = time.time() - time_start | |
66 | if statsd: |
|
87 | if statsd: | |
67 | operation_took_ms = round(1000.0 * operation_took) |
|
88 | operation_took_ms = round(1000.0 * operation_took) | |
68 | statsd.timing("rhodecode_ssh_wrapper_timing.histogram", operation_took_ms, |
|
89 | statsd.timing("rhodecode_ssh_wrapper_timing.histogram", operation_took_ms, | |
69 | use_decimals=False) |
|
90 | use_decimals=False) | |
|
91 | ||||
70 | sys.exit(return_code) |
|
92 | sys.exit(return_code) |
@@ -17,11 +17,11 b'' | |||||
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ |
|
17 | # and proprietary license terms, please see https://rhodecode.com/licenses/ | |
18 |
|
18 | |||
19 | import logging |
|
19 | import logging | |
20 | from pyramid.paster import setup_logging |
|
|||
21 |
|
20 | |||
22 |
|
21 | |||
23 | def setup_custom_logging(ini_path, debug): |
|
22 | def setup_custom_logging(ini_path, debug): | |
24 | if debug: |
|
23 | if debug: | |
|
24 | from pyramid.paster import setup_logging # Lazy import | |||
25 | # enabled rhodecode.ini controlled logging setup |
|
25 | # enabled rhodecode.ini controlled logging setup | |
26 | setup_logging(ini_path) |
|
26 | setup_logging(ini_path) | |
27 | else: |
|
27 | else: |
@@ -52,7 +52,10 b' def dummy_env():' | |||||
52 |
|
52 | |||
53 |
|
53 | |||
54 | def plain_dummy_user(): |
|
54 | def plain_dummy_user(): | |
55 |
return AttributeDict( |
|
55 | return AttributeDict( | |
|
56 | user_id=1, | |||
|
57 | username='test_user' | |||
|
58 | ) | |||
56 |
|
59 | |||
57 |
|
60 | |||
58 | @pytest.fixture() |
|
61 | @pytest.fixture() | |
@@ -65,4 +68,4 b' def ssh_wrapper(app, dummy_conf_file, du' | |||||
65 | conn_info = '127.0.0.1 22 10.0.0.1 443' |
|
68 | conn_info = '127.0.0.1 22 10.0.0.1 443' | |
66 | return SshWrapper( |
|
69 | return SshWrapper( | |
67 | 'random command', conn_info, 'auto', 'admin', '1', key_id='1', |
|
70 | 'random command', conn_info, 'auto', 'admin', '1', key_id='1', | |
68 | shell=False, ini_path=dummy_conf_file, env=dummy_env) |
|
71 | shell=False, ini_path=dummy_conf_file, settings={}, env=dummy_env) |
@@ -25,6 +25,7 b' from rhodecode.apps.ssh_support.lib.back' | |||||
25 | from rhodecode.apps.ssh_support.tests.conftest import plain_dummy_env, plain_dummy_user |
|
25 | from rhodecode.apps.ssh_support.tests.conftest import plain_dummy_env, plain_dummy_user | |
26 | from rhodecode.lib.ext_json import json |
|
26 | from rhodecode.lib.ext_json import json | |
27 |
|
27 | |||
|
28 | ||||
28 | class GitServerCreator(object): |
|
29 | class GitServerCreator(object): | |
29 | root = '/tmp/repo/path/' |
|
30 | root = '/tmp/repo/path/' | |
30 | git_path = '/usr/local/bin/git' |
|
31 | git_path = '/usr/local/bin/git' | |
@@ -39,10 +40,7 b' class GitServerCreator(object):' | |||||
39 | user = plain_dummy_user() |
|
40 | user = plain_dummy_user() | |
40 |
|
41 | |||
41 | def __init__(self): |
|
42 | def __init__(self): | |
42 | def config_get(part, key): |
|
43 | pass | |
43 | return self.config_data.get(part, {}).get(key) |
|
|||
44 | self.config_mock = mock.Mock() |
|
|||
45 | self.config_mock.get = mock.Mock(side_effect=config_get) |
|
|||
46 |
|
44 | |||
47 | def create(self, **kwargs): |
|
45 | def create(self, **kwargs): | |
48 | parameters = { |
|
46 | parameters = { | |
@@ -54,7 +52,7 b' class GitServerCreator(object):' | |||||
54 | 'user_permissions': { |
|
52 | 'user_permissions': { | |
55 | self.repo_name: 'repository.admin' |
|
53 | self.repo_name: 'repository.admin' | |
56 | }, |
|
54 | }, | |
57 |
' |
|
55 | 'settings': self.config_data['app:main'], | |
58 | 'env': plain_dummy_env() |
|
56 | 'env': plain_dummy_env() | |
59 | } |
|
57 | } | |
60 | parameters.update(kwargs) |
|
58 | parameters.update(kwargs) | |
@@ -142,7 +140,7 b' class TestGitServer(object):' | |||||
142 | 'server_url': None, |
|
140 | 'server_url': None, | |
143 | 'hooks': ['push', 'pull'], |
|
141 | 'hooks': ['push', 'pull'], | |
144 | 'is_shadow_repo': False, |
|
142 | 'is_shadow_repo': False, | |
145 |
'hooks_module': 'rhodecode.lib.hook |
|
143 | 'hooks_module': 'rhodecode.lib.hook_daemon.hook_module', | |
146 | 'check_branch_perms': False, |
|
144 | 'check_branch_perms': False, | |
147 | 'detect_force_push': False, |
|
145 | 'detect_force_push': False, | |
148 | 'user_agent': u'git/ssh-user-agent', |
|
146 | 'user_agent': u'git/ssh-user-agent', |
@@ -38,10 +38,7 b' class MercurialServerCreator(object):' | |||||
38 | user = plain_dummy_user() |
|
38 | user = plain_dummy_user() | |
39 |
|
39 | |||
40 | def __init__(self): |
|
40 | def __init__(self): | |
41 | def config_get(part, key): |
|
41 | pass | |
42 | return self.config_data.get(part, {}).get(key) |
|
|||
43 | self.config_mock = mock.Mock() |
|
|||
44 | self.config_mock.get = mock.Mock(side_effect=config_get) |
|
|||
45 |
|
42 | |||
46 | def create(self, **kwargs): |
|
43 | def create(self, **kwargs): | |
47 | parameters = { |
|
44 | parameters = { | |
@@ -52,7 +49,7 b' class MercurialServerCreator(object):' | |||||
52 | 'user_permissions': { |
|
49 | 'user_permissions': { | |
53 | 'test_hg': 'repository.admin' |
|
50 | 'test_hg': 'repository.admin' | |
54 | }, |
|
51 | }, | |
55 |
' |
|
52 | 'settings': self.config_data['app:main'], | |
56 | 'env': plain_dummy_env() |
|
53 | 'env': plain_dummy_env() | |
57 | } |
|
54 | } | |
58 | parameters.update(kwargs) |
|
55 | parameters.update(kwargs) |
@@ -36,10 +36,7 b' class SubversionServerCreator(object):' | |||||
36 | user = plain_dummy_user() |
|
36 | user = plain_dummy_user() | |
37 |
|
37 | |||
38 | def __init__(self): |
|
38 | def __init__(self): | |
39 | def config_get(part, key): |
|
39 | pass | |
40 | return self.config_data.get(part, {}).get(key) |
|
|||
41 | self.config_mock = mock.Mock() |
|
|||
42 | self.config_mock.get = mock.Mock(side_effect=config_get) |
|
|||
43 |
|
40 | |||
44 | def create(self, **kwargs): |
|
41 | def create(self, **kwargs): | |
45 | parameters = { |
|
42 | parameters = { | |
@@ -50,7 +47,7 b' class SubversionServerCreator(object):' | |||||
50 | 'user_permissions': { |
|
47 | 'user_permissions': { | |
51 | self.repo_name: 'repository.admin' |
|
48 | self.repo_name: 'repository.admin' | |
52 | }, |
|
49 | }, | |
53 |
' |
|
50 | 'settings': self.config_data['app:main'], | |
54 | 'env': plain_dummy_env() |
|
51 | 'env': plain_dummy_env() | |
55 | } |
|
52 | } | |
56 |
|
53 |
@@ -28,10 +28,6 b' class TestSSHWrapper(object):' | |||||
28 | permissions={}, branch_permissions={}) |
|
28 | permissions={}, branch_permissions={}) | |
29 | assert str(exc_info.value) == 'Unrecognised VCS: microsoft-tfs' |
|
29 | assert str(exc_info.value) == 'Unrecognised VCS: microsoft-tfs' | |
30 |
|
30 | |||
31 | def test_parse_config(self, ssh_wrapper): |
|
|||
32 | config = ssh_wrapper.parse_config(ssh_wrapper.ini_path) |
|
|||
33 | assert config |
|
|||
34 |
|
||||
35 | def test_get_connection_info(self, ssh_wrapper): |
|
31 | def test_get_connection_info(self, ssh_wrapper): | |
36 | conn_info = ssh_wrapper.get_connection_info() |
|
32 | conn_info = ssh_wrapper.get_connection_info() | |
37 | assert {'client_ip': '127.0.0.1', |
|
33 | assert {'client_ip': '127.0.0.1', |
@@ -19,7 +19,7 b'' | |||||
19 | import os |
|
19 | import os | |
20 | import sys |
|
20 | import sys | |
21 | import collections |
|
21 | import collections | |
22 | import tempfile |
|
22 | ||
23 | import time |
|
23 | import time | |
24 | import logging.config |
|
24 | import logging.config | |
25 |
|
25 | |||
@@ -32,14 +32,13 b' from pyramid.httpexceptions import (' | |||||
32 | HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound) |
|
32 | HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound) | |
33 | from pyramid.renderers import render_to_response |
|
33 | from pyramid.renderers import render_to_response | |
34 |
|
34 | |||
35 | from rhodecode import api |
|
|||
36 | from rhodecode.model import meta |
|
35 | from rhodecode.model import meta | |
37 | from rhodecode.config import patches |
|
36 | from rhodecode.config import patches | |
38 | from rhodecode.config import utils as config_utils |
|
37 | ||
39 | from rhodecode.config.settings_maker import SettingsMaker |
|
|||
40 | from rhodecode.config.environment import load_pyramid_environment |
|
38 | from rhodecode.config.environment import load_pyramid_environment | |
41 |
|
39 | |||
42 | import rhodecode.events |
|
40 | import rhodecode.events | |
|
41 | from rhodecode.config.config_maker import sanitize_settings_and_apply_defaults | |||
43 | from rhodecode.lib.middleware.vcs import VCSMiddleware |
|
42 | from rhodecode.lib.middleware.vcs import VCSMiddleware | |
44 | from rhodecode.lib.request import Request |
|
43 | from rhodecode.lib.request import Request | |
45 | from rhodecode.lib.vcs import VCSCommunicationError |
|
44 | from rhodecode.lib.vcs import VCSCommunicationError | |
@@ -465,173 +464,3 b' def wrap_app_in_wsgi_middlewares(pyramid' | |||||
465 | log.debug('Request processing finalized: %.4fs', total) |
|
464 | log.debug('Request processing finalized: %.4fs', total) | |
466 |
|
465 | |||
467 | return pyramid_app_with_cleanup |
|
466 | return pyramid_app_with_cleanup | |
468 |
|
||||
469 |
|
||||
470 | def sanitize_settings_and_apply_defaults(global_config, settings): |
|
|||
471 | """ |
|
|||
472 | Applies settings defaults and does all type conversion. |
|
|||
473 |
|
||||
474 | We would move all settings parsing and preparation into this place, so that |
|
|||
475 | we have only one place left which deals with this part. The remaining parts |
|
|||
476 | of the application would start to rely fully on well prepared settings. |
|
|||
477 |
|
||||
478 | This piece would later be split up per topic to avoid a big fat monster |
|
|||
479 | function. |
|
|||
480 | """ |
|
|||
481 | jn = os.path.join |
|
|||
482 |
|
||||
483 | global_settings_maker = SettingsMaker(global_config) |
|
|||
484 | global_settings_maker.make_setting('debug', default=False, parser='bool') |
|
|||
485 | debug_enabled = asbool(global_config.get('debug')) |
|
|||
486 |
|
||||
487 | settings_maker = SettingsMaker(settings) |
|
|||
488 |
|
||||
489 | settings_maker.make_setting( |
|
|||
490 | 'logging.autoconfigure', |
|
|||
491 | default=False, |
|
|||
492 | parser='bool') |
|
|||
493 |
|
||||
494 | logging_conf = jn(os.path.dirname(global_config.get('__file__')), 'logging.ini') |
|
|||
495 | settings_maker.enable_logging(logging_conf, level='INFO' if debug_enabled else 'DEBUG') |
|
|||
496 |
|
||||
497 | # Default includes, possible to change as a user |
|
|||
498 | pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline') |
|
|||
499 | log.debug( |
|
|||
500 | "Using the following pyramid.includes: %s", |
|
|||
501 | pyramid_includes) |
|
|||
502 |
|
||||
503 | settings_maker.make_setting('rhodecode.edition', 'Community Edition') |
|
|||
504 | settings_maker.make_setting('rhodecode.edition_id', 'CE') |
|
|||
505 |
|
||||
506 | if 'mako.default_filters' not in settings: |
|
|||
507 | # set custom default filters if we don't have it defined |
|
|||
508 | settings['mako.imports'] = 'from rhodecode.lib.base import h_filter' |
|
|||
509 | settings['mako.default_filters'] = 'h_filter' |
|
|||
510 |
|
||||
511 | if 'mako.directories' not in settings: |
|
|||
512 | mako_directories = settings.setdefault('mako.directories', [ |
|
|||
513 | # Base templates of the original application |
|
|||
514 | 'rhodecode:templates', |
|
|||
515 | ]) |
|
|||
516 | log.debug( |
|
|||
517 | "Using the following Mako template directories: %s", |
|
|||
518 | mako_directories) |
|
|||
519 |
|
||||
520 | # NOTE(marcink): fix redis requirement for schema of connection since 3.X |
|
|||
521 | if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis': |
|
|||
522 | raw_url = settings['beaker.session.url'] |
|
|||
523 | if not raw_url.startswith(('redis://', 'rediss://', 'unix://')): |
|
|||
524 | settings['beaker.session.url'] = 'redis://' + raw_url |
|
|||
525 |
|
||||
526 | settings_maker.make_setting('__file__', global_config.get('__file__')) |
|
|||
527 |
|
||||
528 | # TODO: johbo: Re-think this, usually the call to config.include |
|
|||
529 | # should allow to pass in a prefix. |
|
|||
530 | settings_maker.make_setting('rhodecode.api.url', api.DEFAULT_URL) |
|
|||
531 |
|
||||
532 | # Sanitize generic settings. |
|
|||
533 | settings_maker.make_setting('default_encoding', 'UTF-8', parser='list') |
|
|||
534 | settings_maker.make_setting('is_test', False, parser='bool') |
|
|||
535 | settings_maker.make_setting('gzip_responses', False, parser='bool') |
|
|||
536 |
|
||||
537 | # statsd |
|
|||
538 | settings_maker.make_setting('statsd.enabled', False, parser='bool') |
|
|||
539 | settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string') |
|
|||
540 | settings_maker.make_setting('statsd.statsd_port', 9125, parser='int') |
|
|||
541 | settings_maker.make_setting('statsd.statsd_prefix', '') |
|
|||
542 | settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool') |
|
|||
543 |
|
||||
544 | settings_maker.make_setting('vcs.svn.compatible_version', '') |
|
|||
545 | settings_maker.make_setting('vcs.hooks.protocol', 'http') |
|
|||
546 | settings_maker.make_setting('vcs.hooks.host', '*') |
|
|||
547 | settings_maker.make_setting('vcs.scm_app_implementation', 'http') |
|
|||
548 | settings_maker.make_setting('vcs.server', '') |
|
|||
549 | settings_maker.make_setting('vcs.server.protocol', 'http') |
|
|||
550 | settings_maker.make_setting('vcs.server.enable', 'true', parser='bool') |
|
|||
551 | settings_maker.make_setting('startup.import_repos', 'false', parser='bool') |
|
|||
552 | settings_maker.make_setting('vcs.hooks.direct_calls', 'false', parser='bool') |
|
|||
553 | settings_maker.make_setting('vcs.start_server', 'false', parser='bool') |
|
|||
554 | settings_maker.make_setting('vcs.backends', 'hg, git, svn', parser='list') |
|
|||
555 | settings_maker.make_setting('vcs.connection_timeout', 3600, parser='int') |
|
|||
556 |
|
||||
557 | settings_maker.make_setting('vcs.methods.cache', True, parser='bool') |
|
|||
558 |
|
||||
559 | # Support legacy values of vcs.scm_app_implementation. Legacy |
|
|||
560 | # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or |
|
|||
561 | # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'. |
|
|||
562 | scm_app_impl = settings['vcs.scm_app_implementation'] |
|
|||
563 | if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']: |
|
|||
564 | settings['vcs.scm_app_implementation'] = 'http' |
|
|||
565 |
|
||||
566 | settings_maker.make_setting('appenlight', False, parser='bool') |
|
|||
567 |
|
||||
568 | temp_store = tempfile.gettempdir() |
|
|||
569 | tmp_cache_dir = jn(temp_store, 'rc_cache') |
|
|||
570 |
|
||||
571 | # save default, cache dir, and use it for all backends later. |
|
|||
572 | default_cache_dir = settings_maker.make_setting( |
|
|||
573 | 'cache_dir', |
|
|||
574 | default=tmp_cache_dir, default_when_empty=True, |
|
|||
575 | parser='dir:ensured') |
|
|||
576 |
|
||||
577 | # exception store cache |
|
|||
578 | settings_maker.make_setting( |
|
|||
579 | 'exception_tracker.store_path', |
|
|||
580 | default=jn(default_cache_dir, 'exc_store'), default_when_empty=True, |
|
|||
581 | parser='dir:ensured' |
|
|||
582 | ) |
|
|||
583 |
|
||||
584 | settings_maker.make_setting( |
|
|||
585 | 'celerybeat-schedule.path', |
|
|||
586 | default=jn(default_cache_dir, 'celerybeat_schedule', 'celerybeat-schedule.db'), default_when_empty=True, |
|
|||
587 | parser='file:ensured' |
|
|||
588 | ) |
|
|||
589 |
|
||||
590 | settings_maker.make_setting('exception_tracker.send_email', False, parser='bool') |
|
|||
591 | settings_maker.make_setting('exception_tracker.email_prefix', '[RHODECODE ERROR]', default_when_empty=True) |
|
|||
592 |
|
||||
593 | # sessions, ensure file since no-value is memory |
|
|||
594 | settings_maker.make_setting('beaker.session.type', 'file') |
|
|||
595 | settings_maker.make_setting('beaker.session.data_dir', jn(default_cache_dir, 'session_data')) |
|
|||
596 |
|
||||
597 | # cache_general |
|
|||
598 | settings_maker.make_setting('rc_cache.cache_general.backend', 'dogpile.cache.rc.file_namespace') |
|
|||
599 | settings_maker.make_setting('rc_cache.cache_general.expiration_time', 60 * 60 * 12, parser='int') |
|
|||
600 | settings_maker.make_setting('rc_cache.cache_general.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_general.db')) |
|
|||
601 |
|
||||
602 | # cache_perms |
|
|||
603 | settings_maker.make_setting('rc_cache.cache_perms.backend', 'dogpile.cache.rc.file_namespace') |
|
|||
604 | settings_maker.make_setting('rc_cache.cache_perms.expiration_time', 60 * 60, parser='int') |
|
|||
605 | settings_maker.make_setting('rc_cache.cache_perms.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_perms_db')) |
|
|||
606 |
|
||||
607 | # cache_repo |
|
|||
608 | settings_maker.make_setting('rc_cache.cache_repo.backend', 'dogpile.cache.rc.file_namespace') |
|
|||
609 | settings_maker.make_setting('rc_cache.cache_repo.expiration_time', 60 * 60 * 24 * 30, parser='int') |
|
|||
610 | settings_maker.make_setting('rc_cache.cache_repo.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_repo_db')) |
|
|||
611 |
|
||||
612 | # cache_license |
|
|||
613 | settings_maker.make_setting('rc_cache.cache_license.backend', 'dogpile.cache.rc.file_namespace') |
|
|||
614 | settings_maker.make_setting('rc_cache.cache_license.expiration_time', 60 * 5, parser='int') |
|
|||
615 | settings_maker.make_setting('rc_cache.cache_license.arguments.filename', jn(default_cache_dir, 'rhodecode_cache_license_db')) |
|
|||
616 |
|
||||
617 | # cache_repo_longterm memory, 96H |
|
|||
618 | settings_maker.make_setting('rc_cache.cache_repo_longterm.backend', 'dogpile.cache.rc.memory_lru') |
|
|||
619 | settings_maker.make_setting('rc_cache.cache_repo_longterm.expiration_time', 345600, parser='int') |
|
|||
620 | settings_maker.make_setting('rc_cache.cache_repo_longterm.max_size', 10000, parser='int') |
|
|||
621 |
|
||||
622 | # sql_cache_short |
|
|||
623 | settings_maker.make_setting('rc_cache.sql_cache_short.backend', 'dogpile.cache.rc.memory_lru') |
|
|||
624 | settings_maker.make_setting('rc_cache.sql_cache_short.expiration_time', 30, parser='int') |
|
|||
625 | settings_maker.make_setting('rc_cache.sql_cache_short.max_size', 10000, parser='int') |
|
|||
626 |
|
||||
627 | # archive_cache |
|
|||
628 | settings_maker.make_setting('archive_cache.store_dir', jn(default_cache_dir, 'archive_cache'), default_when_empty=True,) |
|
|||
629 | settings_maker.make_setting('archive_cache.cache_size_gb', 10, parser='float') |
|
|||
630 | settings_maker.make_setting('archive_cache.cache_shards', 10, parser='int') |
|
|||
631 |
|
||||
632 | settings_maker.env_expand() |
|
|||
633 |
|
||||
634 | # configure instance id |
|
|||
635 | config_utils.set_instance_id(settings) |
|
|||
636 |
|
||||
637 | return settings |
|
@@ -19,8 +19,6 b'' | |||||
19 | import os |
|
19 | import os | |
20 | import platform |
|
20 | import platform | |
21 |
|
21 | |||
22 | from rhodecode.model import init_model |
|
|||
23 |
|
||||
24 |
|
22 | |||
25 | def configure_vcs(config): |
|
23 | def configure_vcs(config): | |
26 | """ |
|
24 | """ | |
@@ -44,6 +42,7 b' def configure_vcs(config):' | |||||
44 |
|
42 | |||
45 | def initialize_database(config): |
|
43 | def initialize_database(config): | |
46 | from rhodecode.lib.utils2 import engine_from_config, get_encryption_key |
|
44 | from rhodecode.lib.utils2 import engine_from_config, get_encryption_key | |
|
45 | from rhodecode.model import init_model | |||
47 | engine = engine_from_config(config, 'sqlalchemy.db1.') |
|
46 | engine = engine_from_config(config, 'sqlalchemy.db1.') | |
48 | init_model(engine, encryption_key=get_encryption_key(config)) |
|
47 | init_model(engine, encryption_key=get_encryption_key(config)) | |
49 |
|
48 |
@@ -567,7 +567,7 b' def add_events_routes(config):' | |||||
567 |
|
567 | |||
568 |
|
568 | |||
569 | def bootstrap_config(request, registry_name='RcTestRegistry'): |
|
569 | def bootstrap_config(request, registry_name='RcTestRegistry'): | |
570 |
from rhodecode.config. |
|
570 | from rhodecode.config.config_maker import sanitize_settings_and_apply_defaults | |
571 | import pyramid.testing |
|
571 | import pyramid.testing | |
572 | registry = pyramid.testing.Registry(registry_name) |
|
572 | registry = pyramid.testing.Registry(registry_name) | |
573 |
|
573 |
@@ -34,8 +34,7 b' from rhodecode.lib.utils import is_valid' | |||||
34 | from rhodecode.lib.str_utils import safe_str, safe_int, safe_bytes |
|
34 | from rhodecode.lib.str_utils import safe_str, safe_int, safe_bytes | |
35 | from rhodecode.lib.type_utils import str2bool |
|
35 | from rhodecode.lib.type_utils import str2bool | |
36 | from rhodecode.lib.ext_json import json |
|
36 | from rhodecode.lib.ext_json import json | |
37 |
from rhodecode.lib.hook |
|
37 | from rhodecode.lib.hook_daemon.base import store_txn_id_data | |
38 |
|
||||
39 |
|
38 | |||
40 | log = logging.getLogger(__name__) |
|
39 | log = logging.getLogger(__name__) | |
41 |
|
40 |
@@ -45,7 +45,7 b' from rhodecode.lib.auth import AuthUser,' | |||||
45 | from rhodecode.lib.base import ( |
|
45 | from rhodecode.lib.base import ( | |
46 | BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context) |
|
46 | BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context) | |
47 | from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError) |
|
47 | from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError) | |
48 |
from rhodecode.lib.hook |
|
48 | from rhodecode.lib.hook_daemon.base import prepare_callback_daemon | |
49 | from rhodecode.lib.middleware import appenlight |
|
49 | from rhodecode.lib.middleware import appenlight | |
50 | from rhodecode.lib.middleware.utils import scm_app_http |
|
50 | from rhodecode.lib.middleware.utils import scm_app_http | |
51 | from rhodecode.lib.str_utils import safe_bytes |
|
51 | from rhodecode.lib.str_utils import safe_bytes |
@@ -20,24 +20,14 b'' | |||||
20 |
|
20 | |||
21 | import os |
|
21 | import os | |
22 | import configparser |
|
22 | import configparser | |
|
23 | ||||
|
24 | from rhodecode.lib.config_utils import get_config | |||
23 | from pyramid.paster import bootstrap as pyramid_bootstrap, setup_logging # pragma: no cover |
|
25 | from pyramid.paster import bootstrap as pyramid_bootstrap, setup_logging # pragma: no cover | |
24 |
|
26 | |||
25 | from rhodecode.lib.request import Request |
|
|||
26 |
|
||||
27 |
|
||||
28 | def get_config(ini_path, **kwargs): |
|
|||
29 | parser = configparser.ConfigParser(**kwargs) |
|
|||
30 | parser.read(ini_path) |
|
|||
31 | return parser |
|
|||
32 |
|
||||
33 |
|
||||
34 | def get_app_config(ini_path): |
|
|||
35 | from paste.deploy.loadwsgi import appconfig |
|
|||
36 | return appconfig(f'config:{ini_path}', relative_to=os.getcwd()) |
|
|||
37 |
|
||||
38 |
|
27 | |||
39 | def bootstrap(config_uri, options=None, env=None): |
|
28 | def bootstrap(config_uri, options=None, env=None): | |
40 | from rhodecode.lib.utils2 import AttributeDict |
|
29 | from rhodecode.lib.utils2 import AttributeDict | |
|
30 | from rhodecode.lib.request import Request | |||
41 |
|
31 | |||
42 | if env: |
|
32 | if env: | |
43 | os.environ.update(env) |
|
33 | os.environ.update(env) |
@@ -20,7 +20,8 b' import logging' | |||||
20 | import click |
|
20 | import click | |
21 | import pyramid.paster |
|
21 | import pyramid.paster | |
22 |
|
22 | |||
23 |
from rhodecode.lib.pyramid_utils import bootstrap |
|
23 | from rhodecode.lib.pyramid_utils import bootstrap | |
|
24 | from rhodecode.lib.config_utils import get_app_config | |||
24 | from rhodecode.lib.db_manage import DbManage |
|
25 | from rhodecode.lib.db_manage import DbManage | |
25 | from rhodecode.lib.utils2 import get_encryption_key |
|
26 | from rhodecode.lib.utils2 import get_encryption_key | |
26 | from rhodecode.model.db import Session |
|
27 | from rhodecode.model.db import Session |
@@ -32,11 +32,9 b' import socket' | |||||
32 | import tempfile |
|
32 | import tempfile | |
33 | import traceback |
|
33 | import traceback | |
34 | import tarfile |
|
34 | import tarfile | |
35 | import urllib.parse |
|
35 | ||
36 | import warnings |
|
|||
37 | from functools import wraps |
|
36 | from functools import wraps | |
38 | from os.path import join as jn |
|
37 | from os.path import join as jn | |
39 | from configparser import NoOptionError |
|
|||
40 |
|
38 | |||
41 | import paste |
|
39 | import paste | |
42 | import pkg_resources |
|
40 | import pkg_resources | |
@@ -55,9 +53,6 b' from rhodecode.model import meta' | |||||
55 | from rhodecode.model.db import ( |
|
53 | from rhodecode.model.db import ( | |
56 | Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup) |
|
54 | Repository, User, RhodeCodeUi, UserLog, RepoGroup, UserGroup) | |
57 | from rhodecode.model.meta import Session |
|
55 | from rhodecode.model.meta import Session | |
58 | from rhodecode.lib.pyramid_utils import get_config |
|
|||
59 | from rhodecode.lib.vcs import CurlSession |
|
|||
60 | from rhodecode.lib.vcs.exceptions import ImproperlyConfiguredError |
|
|||
61 |
|
56 | |||
62 |
|
57 | |||
63 | log = logging.getLogger(__name__) |
|
58 | log = logging.getLogger(__name__) | |
@@ -827,33 +822,3 b' def send_test_email(recipients, email_bo' | |||||
827 | email_body = email_body_plaintext = email_body |
|
822 | email_body = email_body_plaintext = email_body | |
828 | subject = f'SUBJECT FROM: {socket.gethostname()}' |
|
823 | subject = f'SUBJECT FROM: {socket.gethostname()}' | |
829 | tasks.send_email(recipients, subject, email_body_plaintext, email_body) |
|
824 | tasks.send_email(recipients, subject, email_body_plaintext, email_body) | |
830 |
|
||||
831 |
|
||||
832 | def call_service_api(ini_path, payload): |
|
|||
833 | config = get_config(ini_path) |
|
|||
834 | try: |
|
|||
835 | host = config.get('app:main', 'app.service_api.host') |
|
|||
836 | except NoOptionError: |
|
|||
837 | raise ImproperlyConfiguredError( |
|
|||
838 | "app.service_api.host is missing. " |
|
|||
839 | "Please ensure that app.service_api.host and app.service_api.token are " |
|
|||
840 | "defined inside of .ini configuration file." |
|
|||
841 | ) |
|
|||
842 | try: |
|
|||
843 | api_url = config.get('app:main', 'rhodecode.api.url') |
|
|||
844 | except NoOptionError: |
|
|||
845 | from rhodecode import api |
|
|||
846 | log.debug('Cannot find rhodecode.api.url, setting API URL TO Default value') |
|
|||
847 | api_url = api.DEFAULT_URL |
|
|||
848 |
|
||||
849 | payload.update({ |
|
|||
850 | 'id': 'service', |
|
|||
851 | 'auth_token': config.get('app:main', 'app.service_api.token') |
|
|||
852 | }) |
|
|||
853 |
|
||||
854 | response = CurlSession().post(urllib.parse.urljoin(host, api_url), json.dumps(payload)) |
|
|||
855 |
|
||||
856 | if response.status_code != 200: |
|
|||
857 | raise Exception("Service API responded with error") |
|
|||
858 |
|
||||
859 | return json.loads(response.content)['result'] |
|
@@ -38,7 +38,7 b' from rhodecode.translation import lazy_u' | |||||
38 | from rhodecode.lib import helpers as h, hooks_utils, diffs |
|
38 | from rhodecode.lib import helpers as h, hooks_utils, diffs | |
39 | from rhodecode.lib import audit_logger |
|
39 | from rhodecode.lib import audit_logger | |
40 | from collections import OrderedDict |
|
40 | from collections import OrderedDict | |
41 |
from rhodecode.lib.hook |
|
41 | from rhodecode.lib.hook_daemon.base import prepare_callback_daemon | |
42 | from rhodecode.lib.ext_json import sjson as json |
|
42 | from rhodecode.lib.ext_json import sjson as json | |
43 | from rhodecode.lib.markup_renderer import ( |
|
43 | from rhodecode.lib.markup_renderer import ( | |
44 | DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer) |
|
44 | DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer) |
@@ -19,7 +19,7 b'' | |||||
19 |
|
19 | |||
20 | import pytest |
|
20 | import pytest | |
21 |
|
21 | |||
22 |
from rhodecode.lib. |
|
22 | from rhodecode.lib.config_utils import get_app_config | |
23 | from rhodecode.tests.fixture import TestINI |
|
23 | from rhodecode.tests.fixture import TestINI | |
24 | from rhodecode.tests.server_utils import RcVCSServer |
|
24 | from rhodecode.tests.server_utils import RcVCSServer | |
25 |
|
25 |
@@ -174,7 +174,7 b' def http_environ():' | |||||
174 |
|
174 | |||
175 | @pytest.fixture(scope='session') |
|
175 | @pytest.fixture(scope='session') | |
176 | def baseapp(ini_config, vcsserver, http_environ_session): |
|
176 | def baseapp(ini_config, vcsserver, http_environ_session): | |
177 |
from rhodecode.lib. |
|
177 | from rhodecode.lib.config_utils import get_app_config | |
178 | from rhodecode.config.middleware import make_pyramid_app |
|
178 | from rhodecode.config.middleware import make_pyramid_app | |
179 |
|
179 | |||
180 | log.info("Using the RhodeCode configuration:{}".format(ini_config)) |
|
180 | log.info("Using the RhodeCode configuration:{}".format(ini_config)) |
@@ -25,17 +25,20 b' import msgpack' | |||||
25 | import pytest |
|
25 | import pytest | |
26 | import tempfile |
|
26 | import tempfile | |
27 |
|
27 | |||
28 |
from rhodecode.lib import hooks_d |
|
28 | from rhodecode.lib.hook_daemon import http_hooks_deamon | |
|
29 | from rhodecode.lib.hook_daemon import celery_hooks_deamon | |||
|
30 | from rhodecode.lib.hook_daemon import hook_module | |||
|
31 | from rhodecode.lib.hook_daemon import base as hook_base | |||
29 | from rhodecode.lib.str_utils import safe_bytes |
|
32 | from rhodecode.lib.str_utils import safe_bytes | |
30 | from rhodecode.tests.utils import assert_message_in_log |
|
33 | from rhodecode.tests.utils import assert_message_in_log | |
31 | from rhodecode.lib.ext_json import json |
|
34 | from rhodecode.lib.ext_json import json | |
32 |
|
35 | |||
33 |
test_proto = hooks_d |
|
36 | test_proto = http_hooks_deamon.HooksHttpHandler.MSGPACK_HOOKS_PROTO | |
34 |
|
37 | |||
35 |
|
38 | |||
36 | class TestHooks(object): |
|
39 | class TestHooks(object): | |
37 | def test_hooks_can_be_used_as_a_context_processor(self): |
|
40 | def test_hooks_can_be_used_as_a_context_processor(self): | |
38 |
hooks = hook |
|
41 | hooks = hook_module.Hooks() | |
39 | with hooks as return_value: |
|
42 | with hooks as return_value: | |
40 | pass |
|
43 | pass | |
41 | assert hooks == return_value |
|
44 | assert hooks == return_value | |
@@ -52,10 +55,10 b' class TestHooksHttpHandler(object):' | |||||
52 | } |
|
55 | } | |
53 | request = self._generate_post_request(data) |
|
56 | request = self._generate_post_request(data) | |
54 | hooks_patcher = mock.patch.object( |
|
57 | hooks_patcher = mock.patch.object( | |
55 |
hook |
|
58 | hook_module.Hooks, data['method'], create=True, return_value=1) | |
56 |
|
59 | |||
57 | with hooks_patcher as hooks_mock: |
|
60 | with hooks_patcher as hooks_mock: | |
58 |
handler = hooks_d |
|
61 | handler = http_hooks_deamon.HooksHttpHandler | |
59 | handler.DEFAULT_HOOKS_PROTO = test_proto |
|
62 | handler.DEFAULT_HOOKS_PROTO = test_proto | |
60 | handler.wbufsize = 10240 |
|
63 | handler.wbufsize = 10240 | |
61 | MockServer(handler, request) |
|
64 | MockServer(handler, request) | |
@@ -73,21 +76,21 b' class TestHooksHttpHandler(object):' | |||||
73 |
|
76 | |||
74 | # patching our _read to return test method and proto used |
|
77 | # patching our _read to return test method and proto used | |
75 | read_patcher = mock.patch.object( |
|
78 | read_patcher = mock.patch.object( | |
76 |
hooks_d |
|
79 | http_hooks_deamon.HooksHttpHandler, '_read_request', | |
77 | return_value=(test_proto, rpc_method, extras)) |
|
80 | return_value=(test_proto, rpc_method, extras)) | |
78 |
|
81 | |||
79 | # patch Hooks instance to return hook_result data on 'test' call |
|
82 | # patch Hooks instance to return hook_result data on 'test' call | |
80 | hooks_patcher = mock.patch.object( |
|
83 | hooks_patcher = mock.patch.object( | |
81 |
hook |
|
84 | hook_module.Hooks, rpc_method, create=True, | |
82 | return_value=hook_result) |
|
85 | return_value=hook_result) | |
83 |
|
86 | |||
84 | with read_patcher, hooks_patcher: |
|
87 | with read_patcher, hooks_patcher: | |
85 |
handler = hooks_d |
|
88 | handler = http_hooks_deamon.HooksHttpHandler | |
86 | handler.DEFAULT_HOOKS_PROTO = test_proto |
|
89 | handler.DEFAULT_HOOKS_PROTO = test_proto | |
87 | handler.wbufsize = 10240 |
|
90 | handler.wbufsize = 10240 | |
88 | server = MockServer(handler, request) |
|
91 | server = MockServer(handler, request) | |
89 |
|
92 | |||
90 |
expected_result = hooks_d |
|
93 | expected_result = http_hooks_deamon.HooksHttpHandler.serialize_data(hook_result) | |
91 |
|
94 | |||
92 | server.request.output_stream.seek(0) |
|
95 | server.request.output_stream.seek(0) | |
93 | assert server.request.output_stream.readlines()[-1] == expected_result |
|
96 | assert server.request.output_stream.readlines()[-1] == expected_result | |
@@ -97,15 +100,15 b' class TestHooksHttpHandler(object):' | |||||
97 | rpc_method = 'test' |
|
100 | rpc_method = 'test' | |
98 |
|
101 | |||
99 | read_patcher = mock.patch.object( |
|
102 | read_patcher = mock.patch.object( | |
100 |
hooks_d |
|
103 | http_hooks_deamon.HooksHttpHandler, '_read_request', | |
101 | return_value=(test_proto, rpc_method, {})) |
|
104 | return_value=(test_proto, rpc_method, {})) | |
102 |
|
105 | |||
103 | hooks_patcher = mock.patch.object( |
|
106 | hooks_patcher = mock.patch.object( | |
104 |
hook |
|
107 | hook_module.Hooks, rpc_method, create=True, | |
105 | side_effect=Exception('Test exception')) |
|
108 | side_effect=Exception('Test exception')) | |
106 |
|
109 | |||
107 | with read_patcher, hooks_patcher: |
|
110 | with read_patcher, hooks_patcher: | |
108 |
handler = hooks_d |
|
111 | handler = http_hooks_deamon.HooksHttpHandler | |
109 | handler.DEFAULT_HOOKS_PROTO = test_proto |
|
112 | handler.DEFAULT_HOOKS_PROTO = test_proto | |
110 | handler.wbufsize = 10240 |
|
113 | handler.wbufsize = 10240 | |
111 | server = MockServer(handler, request) |
|
114 | server = MockServer(handler, request) | |
@@ -113,7 +116,7 b' class TestHooksHttpHandler(object):' | |||||
113 | server.request.output_stream.seek(0) |
|
116 | server.request.output_stream.seek(0) | |
114 | data = server.request.output_stream.readlines() |
|
117 | data = server.request.output_stream.readlines() | |
115 | msgpack_data = b''.join(data[5:]) |
|
118 | msgpack_data = b''.join(data[5:]) | |
116 |
org_exc = hooks_d |
|
119 | org_exc = http_hooks_deamon.HooksHttpHandler.deserialize_data(msgpack_data) | |
117 | expected_result = { |
|
120 | expected_result = { | |
118 | 'exception': 'Exception', |
|
121 | 'exception': 'Exception', | |
119 | 'exception_traceback': org_exc['exception_traceback'], |
|
122 | 'exception_traceback': org_exc['exception_traceback'], | |
@@ -123,8 +126,7 b' class TestHooksHttpHandler(object):' | |||||
123 |
|
126 | |||
124 | def test_log_message_writes_to_debug_log(self, caplog): |
|
127 | def test_log_message_writes_to_debug_log(self, caplog): | |
125 | ip_port = ('0.0.0.0', 8888) |
|
128 | ip_port = ('0.0.0.0', 8888) | |
126 |
handler = hooks_d |
|
129 | handler = http_hooks_deamon.HooksHttpHandler(MockRequest('POST /'), ip_port, mock.Mock()) | |
127 | MockRequest('POST /'), ip_port, mock.Mock()) |
|
|||
128 | fake_date = '1/Nov/2015 00:00:00' |
|
130 | fake_date = '1/Nov/2015 00:00:00' | |
129 | date_patcher = mock.patch.object( |
|
131 | date_patcher = mock.patch.object( | |
130 | handler, 'log_date_time_string', return_value=fake_date) |
|
132 | handler, 'log_date_time_string', return_value=fake_date) | |
@@ -136,10 +138,10 b' class TestHooksHttpHandler(object):' | |||||
136 |
|
138 | |||
137 | assert_message_in_log( |
|
139 | assert_message_in_log( | |
138 | caplog.records, expected_message, |
|
140 | caplog.records, expected_message, | |
139 |
levelno=logging.DEBUG, module='hooks_d |
|
141 | levelno=logging.DEBUG, module='http_hooks_deamon') | |
140 |
|
142 | |||
141 | def _generate_post_request(self, data, proto=test_proto): |
|
143 | def _generate_post_request(self, data, proto=test_proto): | |
142 |
if proto == hooks_d |
|
144 | if proto == http_hooks_deamon.HooksHttpHandler.MSGPACK_HOOKS_PROTO: | |
143 | payload = msgpack.packb(data) |
|
145 | payload = msgpack.packb(data) | |
144 | else: |
|
146 | else: | |
145 | payload = json.dumps(data) |
|
147 | payload = json.dumps(data) | |
@@ -151,18 +153,18 b' class TestHooksHttpHandler(object):' | |||||
151 | class ThreadedHookCallbackDaemon(object): |
|
153 | class ThreadedHookCallbackDaemon(object): | |
152 | def test_constructor_calls_prepare(self): |
|
154 | def test_constructor_calls_prepare(self): | |
153 | prepare_daemon_patcher = mock.patch.object( |
|
155 | prepare_daemon_patcher = mock.patch.object( | |
154 |
hooks_d |
|
156 | http_hooks_deamon.ThreadedHookCallbackDaemon, '_prepare') | |
155 | with prepare_daemon_patcher as prepare_daemon_mock: |
|
157 | with prepare_daemon_patcher as prepare_daemon_mock: | |
156 |
hooks_d |
|
158 | http_hooks_deamon.ThreadedHookCallbackDaemon() | |
157 | prepare_daemon_mock.assert_called_once_with() |
|
159 | prepare_daemon_mock.assert_called_once_with() | |
158 |
|
160 | |||
159 | def test_run_is_called_on_context_start(self): |
|
161 | def test_run_is_called_on_context_start(self): | |
160 | patchers = mock.patch.multiple( |
|
162 | patchers = mock.patch.multiple( | |
161 |
hooks_d |
|
163 | http_hooks_deamon.ThreadedHookCallbackDaemon, | |
162 | _run=mock.DEFAULT, _prepare=mock.DEFAULT, __exit__=mock.DEFAULT) |
|
164 | _run=mock.DEFAULT, _prepare=mock.DEFAULT, __exit__=mock.DEFAULT) | |
163 |
|
165 | |||
164 | with patchers as mocks: |
|
166 | with patchers as mocks: | |
165 |
daemon = hooks_d |
|
167 | daemon = http_hooks_deamon.ThreadedHookCallbackDaemon() | |
166 | with daemon as daemon_context: |
|
168 | with daemon as daemon_context: | |
167 | pass |
|
169 | pass | |
168 | mocks['_run'].assert_called_once_with() |
|
170 | mocks['_run'].assert_called_once_with() | |
@@ -170,11 +172,11 b' class ThreadedHookCallbackDaemon(object)' | |||||
170 |
|
172 | |||
171 | def test_stop_is_called_on_context_exit(self): |
|
173 | def test_stop_is_called_on_context_exit(self): | |
172 | patchers = mock.patch.multiple( |
|
174 | patchers = mock.patch.multiple( | |
173 |
hooks_d |
|
175 | http_hooks_deamon.ThreadedHookCallbackDaemon, | |
174 | _run=mock.DEFAULT, _prepare=mock.DEFAULT, _stop=mock.DEFAULT) |
|
176 | _run=mock.DEFAULT, _prepare=mock.DEFAULT, _stop=mock.DEFAULT) | |
175 |
|
177 | |||
176 | with patchers as mocks: |
|
178 | with patchers as mocks: | |
177 |
daemon = hooks_d |
|
179 | daemon = http_hooks_deamon.ThreadedHookCallbackDaemon() | |
178 | with daemon as daemon_context: |
|
180 | with daemon as daemon_context: | |
179 | assert mocks['_stop'].call_count == 0 |
|
181 | assert mocks['_stop'].call_count == 0 | |
180 |
|
182 | |||
@@ -185,46 +187,47 b' class ThreadedHookCallbackDaemon(object)' | |||||
185 | class TestHttpHooksCallbackDaemon(object): |
|
187 | class TestHttpHooksCallbackDaemon(object): | |
186 | def test_hooks_callback_generates_new_port(self, caplog): |
|
188 | def test_hooks_callback_generates_new_port(self, caplog): | |
187 | with caplog.at_level(logging.DEBUG): |
|
189 | with caplog.at_level(logging.DEBUG): | |
188 |
daemon = hooks_d |
|
190 | daemon = http_hooks_deamon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881) | |
189 | assert daemon._daemon.server_address == ('127.0.0.1', 8881) |
|
191 | assert daemon._daemon.server_address == ('127.0.0.1', 8881) | |
190 |
|
192 | |||
191 | with caplog.at_level(logging.DEBUG): |
|
193 | with caplog.at_level(logging.DEBUG): | |
192 |
daemon = hooks_d |
|
194 | daemon = http_hooks_deamon.HttpHooksCallbackDaemon(host=None, port=None) | |
193 | assert daemon._daemon.server_address[1] in range(0, 66000) |
|
195 | assert daemon._daemon.server_address[1] in range(0, 66000) | |
194 | assert daemon._daemon.server_address[0] != '127.0.0.1' |
|
196 | assert daemon._daemon.server_address[0] != '127.0.0.1' | |
195 |
|
197 | |||
196 | def test_prepare_inits_daemon_variable(self, tcp_server, caplog): |
|
198 | def test_prepare_inits_daemon_variable(self, tcp_server, caplog): | |
197 | with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG): |
|
199 | with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG): | |
198 |
daemon = hooks_d |
|
200 | daemon = http_hooks_deamon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881) | |
199 | assert daemon._daemon == tcp_server |
|
201 | assert daemon._daemon == tcp_server | |
200 |
|
202 | |||
201 | _, port = tcp_server.server_address |
|
203 | _, port = tcp_server.server_address | |
202 |
|
204 | |||
203 | msg = f"HOOKS: 127.0.0.1:{port} Preparing HTTP callback daemon registering " \ |
|
205 | msg = f"HOOKS: 127.0.0.1:{port} Preparing HTTP callback daemon registering " \ | |
204 |
f"hook object: <class 'rhodecode.lib.hooks_d |
|
206 | f"hook object: <class 'rhodecode.lib.hook_daemon.http_hooks_deamon.HooksHttpHandler'>" | |
205 | assert_message_in_log( |
|
207 | assert_message_in_log( | |
206 |
caplog.records, msg, levelno=logging.DEBUG, module='hooks_d |
|
208 | caplog.records, msg, levelno=logging.DEBUG, module='http_hooks_deamon') | |
207 |
|
209 | |||
208 | def test_prepare_inits_hooks_uri_and_logs_it( |
|
210 | def test_prepare_inits_hooks_uri_and_logs_it( | |
209 | self, tcp_server, caplog): |
|
211 | self, tcp_server, caplog): | |
210 | with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG): |
|
212 | with self._tcp_patcher(tcp_server), caplog.at_level(logging.DEBUG): | |
211 |
daemon = hooks_d |
|
213 | daemon = http_hooks_deamon.HttpHooksCallbackDaemon(host='127.0.0.1', port=8881) | |
212 |
|
214 | |||
213 | _, port = tcp_server.server_address |
|
215 | _, port = tcp_server.server_address | |
214 | expected_uri = '{}:{}'.format('127.0.0.1', port) |
|
216 | expected_uri = '{}:{}'.format('127.0.0.1', port) | |
215 | assert daemon.hooks_uri == expected_uri |
|
217 | assert daemon.hooks_uri == expected_uri | |
216 |
|
218 | |||
217 | msg = f"HOOKS: 127.0.0.1:{port} Preparing HTTP callback daemon registering " \ |
|
219 | msg = f"HOOKS: 127.0.0.1:{port} Preparing HTTP callback daemon registering " \ | |
218 |
f"hook object: <class 'rhodecode.lib.hooks_d |
|
220 | f"hook object: <class 'rhodecode.lib.hook_daemon.http_hooks_deamon.HooksHttpHandler'>" | |
|
221 | ||||
219 | assert_message_in_log( |
|
222 | assert_message_in_log( | |
220 | caplog.records, msg, |
|
223 | caplog.records, msg, | |
221 |
levelno=logging.DEBUG, module='hooks_d |
|
224 | levelno=logging.DEBUG, module='http_hooks_deamon') | |
222 |
|
225 | |||
223 | def test_run_creates_a_thread(self, tcp_server): |
|
226 | def test_run_creates_a_thread(self, tcp_server): | |
224 | thread = mock.Mock() |
|
227 | thread = mock.Mock() | |
225 |
|
228 | |||
226 | with self._tcp_patcher(tcp_server): |
|
229 | with self._tcp_patcher(tcp_server): | |
227 |
daemon = hooks_d |
|
230 | daemon = http_hooks_deamon.HttpHooksCallbackDaemon() | |
228 |
|
231 | |||
229 | with self._thread_patcher(thread) as thread_mock: |
|
232 | with self._thread_patcher(thread) as thread_mock: | |
230 | daemon._run() |
|
233 | daemon._run() | |
@@ -238,7 +241,7 b' class TestHttpHooksCallbackDaemon(object' | |||||
238 | def test_run_logs(self, tcp_server, caplog): |
|
241 | def test_run_logs(self, tcp_server, caplog): | |
239 |
|
242 | |||
240 | with self._tcp_patcher(tcp_server): |
|
243 | with self._tcp_patcher(tcp_server): | |
241 |
daemon = hooks_d |
|
244 | daemon = http_hooks_deamon.HttpHooksCallbackDaemon() | |
242 |
|
245 | |||
243 | with self._thread_patcher(mock.Mock()), caplog.at_level(logging.DEBUG): |
|
246 | with self._thread_patcher(mock.Mock()), caplog.at_level(logging.DEBUG): | |
244 | daemon._run() |
|
247 | daemon._run() | |
@@ -246,13 +249,13 b' class TestHttpHooksCallbackDaemon(object' | |||||
246 | assert_message_in_log( |
|
249 | assert_message_in_log( | |
247 | caplog.records, |
|
250 | caplog.records, | |
248 | 'Running thread-based loop of callback daemon in background', |
|
251 | 'Running thread-based loop of callback daemon in background', | |
249 |
levelno=logging.DEBUG, module='hooks_d |
|
252 | levelno=logging.DEBUG, module='http_hooks_deamon') | |
250 |
|
253 | |||
251 | def test_stop_cleans_up_the_connection(self, tcp_server, caplog): |
|
254 | def test_stop_cleans_up_the_connection(self, tcp_server, caplog): | |
252 | thread = mock.Mock() |
|
255 | thread = mock.Mock() | |
253 |
|
256 | |||
254 | with self._tcp_patcher(tcp_server): |
|
257 | with self._tcp_patcher(tcp_server): | |
255 |
daemon = hooks_d |
|
258 | daemon = http_hooks_deamon.HttpHooksCallbackDaemon() | |
256 |
|
259 | |||
257 | with self._thread_patcher(thread), caplog.at_level(logging.DEBUG): |
|
260 | with self._thread_patcher(thread), caplog.at_level(logging.DEBUG): | |
258 | with daemon: |
|
261 | with daemon: | |
@@ -266,18 +269,19 b' class TestHttpHooksCallbackDaemon(object' | |||||
266 |
|
269 | |||
267 | assert_message_in_log( |
|
270 | assert_message_in_log( | |
268 | caplog.records, 'Waiting for background thread to finish.', |
|
271 | caplog.records, 'Waiting for background thread to finish.', | |
269 |
levelno=logging.DEBUG, module='hooks_d |
|
272 | levelno=logging.DEBUG, module='http_hooks_deamon') | |
270 |
|
273 | |||
271 | def _tcp_patcher(self, tcp_server): |
|
274 | def _tcp_patcher(self, tcp_server): | |
272 | return mock.patch.object( |
|
275 | return mock.patch.object( | |
273 |
hooks_d |
|
276 | http_hooks_deamon, 'TCPServer', return_value=tcp_server) | |
274 |
|
277 | |||
275 | def _thread_patcher(self, thread): |
|
278 | def _thread_patcher(self, thread): | |
276 | return mock.patch.object( |
|
279 | return mock.patch.object( | |
277 |
hooks_d |
|
280 | http_hooks_deamon.threading, 'Thread', return_value=thread) | |
278 |
|
281 | |||
279 |
|
282 | |||
280 | class TestPrepareHooksDaemon(object): |
|
283 | class TestPrepareHooksDaemon(object): | |
|
284 | ||||
281 | @pytest.mark.parametrize('protocol', ('celery',)) |
|
285 | @pytest.mark.parametrize('protocol', ('celery',)) | |
282 | def test_returns_celery_hooks_callback_daemon_when_celery_protocol_specified( |
|
286 | def test_returns_celery_hooks_callback_daemon_when_celery_protocol_specified( | |
283 | self, protocol): |
|
287 | self, protocol): | |
@@ -286,12 +290,12 b' class TestPrepareHooksDaemon(object):' | |||||
286 | "celery.result_backend = redis://redis/0") |
|
290 | "celery.result_backend = redis://redis/0") | |
287 | temp_file.flush() |
|
291 | temp_file.flush() | |
288 | expected_extras = {'config': temp_file.name} |
|
292 | expected_extras = {'config': temp_file.name} | |
289 |
callback, extras = hook |
|
293 | callback, extras = hook_base.prepare_callback_daemon( | |
290 | expected_extras, protocol=protocol, host='') |
|
294 | expected_extras, protocol=protocol, host='') | |
291 |
assert isinstance(callback, hooks_d |
|
295 | assert isinstance(callback, celery_hooks_deamon.CeleryHooksCallbackDaemon) | |
292 |
|
296 | |||
293 | @pytest.mark.parametrize('protocol, expected_class', ( |
|
297 | @pytest.mark.parametrize('protocol, expected_class', ( | |
294 |
('http', hooks_d |
|
298 | ('http', http_hooks_deamon.HttpHooksCallbackDaemon), | |
295 | )) |
|
299 | )) | |
296 | def test_returns_real_hooks_callback_daemon_when_protocol_is_specified( |
|
300 | def test_returns_real_hooks_callback_daemon_when_protocol_is_specified( | |
297 | self, protocol, expected_class): |
|
301 | self, protocol, expected_class): | |
@@ -302,7 +306,7 b' class TestPrepareHooksDaemon(object):' | |||||
302 | 'task_backend': '', |
|
306 | 'task_backend': '', | |
303 | 'task_queue': '' |
|
307 | 'task_queue': '' | |
304 | } |
|
308 | } | |
305 |
callback, extras = hook |
|
309 | callback, extras = hook_base.prepare_callback_daemon( | |
306 | expected_extras.copy(), protocol=protocol, host='127.0.0.1', |
|
310 | expected_extras.copy(), protocol=protocol, host='127.0.0.1', | |
307 | txn_id='txnid2') |
|
311 | txn_id='txnid2') | |
308 | assert isinstance(callback, expected_class) |
|
312 | assert isinstance(callback, expected_class) | |
@@ -321,7 +325,7 b' class TestPrepareHooksDaemon(object):' | |||||
321 | 'hooks_protocol': protocol.lower() |
|
325 | 'hooks_protocol': protocol.lower() | |
322 | } |
|
326 | } | |
323 | with pytest.raises(Exception): |
|
327 | with pytest.raises(Exception): | |
324 |
callback, extras = hook |
|
328 | callback, extras = hook_base.prepare_callback_daemon( | |
325 | expected_extras.copy(), |
|
329 | expected_extras.copy(), | |
326 | protocol=protocol, host='127.0.0.1') |
|
330 | protocol=protocol, host='127.0.0.1') | |
327 |
|
331 |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now