##// END OF EJS Templates
fix(license-import): don't crash if license file is not present, and only run this code for EE
super-admin -
r5538:aa408762 default
parent child Browse files
Show More
@@ -1,414 +1,422 b''
1 1 # Copyright (C) 2010-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18 import io
19 19 import shlex
20 20
21 21 import math
22 22 import re
23 23 import os
24 24 import datetime
25 25 import logging
26 26 import queue
27 27 import subprocess
28 28
29 29
30 30 from dateutil.parser import parse
31 31 from pyramid.interfaces import IRoutesMapper
32 32 from pyramid.settings import asbool
33 33 from pyramid.path import AssetResolver
34 34 from threading import Thread
35 35
36 36 from rhodecode.config.jsroutes import generate_jsroutes_content
37 37 from rhodecode.lib.base import get_auth_user
38 38 from rhodecode.lib.celerylib.loader import set_celery_conf
39 39
40 40 import rhodecode
41 41
42 42
43 43 log = logging.getLogger(__name__)
44 44
45 45
46 46 def add_renderer_globals(event):
47 47 from rhodecode.lib import helpers
48 48
49 49 # TODO: When executed in pyramid view context the request is not available
50 50 # in the event. Find a better solution to get the request.
51 51 from pyramid.threadlocal import get_current_request
52 52 request = event['request'] or get_current_request()
53 53
54 54 # Add Pyramid translation as '_' to context
55 55 event['_'] = request.translate
56 56 event['_ungettext'] = request.plularize
57 57 event['h'] = helpers
58 58
59 59
60 60 def set_user_lang(event):
61 61 request = event.request
62 62 cur_user = getattr(request, 'user', None)
63 63
64 64 if cur_user:
65 65 user_lang = cur_user.get_instance().user_data.get('language')
66 66 if user_lang:
67 67 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
68 68 event.request._LOCALE_ = user_lang
69 69
70 70
71 71 def update_celery_conf(event):
72 72 log.debug('Setting celery config from new request')
73 73 set_celery_conf(request=event.request, registry=event.request.registry)
74 74
75 75
76 76 def add_request_user_context(event):
77 77 """
78 78 Adds auth user into request context
79 79 """
80 80
81 81 request = event.request
82 82 # access req_id as soon as possible
83 83 req_id = request.req_id
84 84
85 85 if hasattr(request, 'vcs_call'):
86 86 # skip vcs calls
87 87 return
88 88
89 89 if hasattr(request, 'rpc_method'):
90 90 # skip api calls
91 91 return
92 92
93 93 auth_user, auth_token = get_auth_user(request)
94 94 request.user = auth_user
95 95 request.user_auth_token = auth_token
96 96 request.environ['rc_auth_user'] = auth_user
97 97 request.environ['rc_auth_user_id'] = str(auth_user.user_id)
98 98 request.environ['rc_req_id'] = req_id
99 99
100 100
101 101 def reset_log_bucket(event):
102 102 """
103 103 reset the log bucket on new request
104 104 """
105 105 request = event.request
106 106 request.req_id_records_init()
107 107
108 108
109 109 def scan_repositories_if_enabled(event):
110 110 """
111 111 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
112 112 does a repository scan if enabled in the settings.
113 113 """
114
114 115 settings = event.app.registry.settings
115 116 vcs_server_enabled = settings['vcs.server.enable']
116 117 import_on_startup = settings['startup.import_repos']
118
117 119 if vcs_server_enabled and import_on_startup:
118 120 from rhodecode.model.scm import ScmModel
119 121 from rhodecode.lib.utils import repo2db_mapper
120 122 scm = ScmModel()
121 123 repositories = scm.repo_scan(scm.repos_path)
122 124 repo2db_mapper(repositories, remove_obsolete=False)
123 125
124 126
125 127 def write_metadata_if_needed(event):
126 128 """
127 129 Writes upgrade metadata
128 130 """
129 131 import rhodecode
130 132 from rhodecode.lib import system_info
131 133 from rhodecode.lib import ext_json
132 134
133 135 fname = '.rcmetadata.json'
134 136 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
135 137 metadata_destination = os.path.join(ini_loc, fname)
136 138
137 139 def get_update_age():
138 140 now = datetime.datetime.utcnow()
139 141
140 142 with open(metadata_destination, 'rb') as f:
141 143 data = ext_json.json.loads(f.read())
142 144 if 'created_on' in data:
143 145 update_date = parse(data['created_on'])
144 146 diff = now - update_date
145 147 return diff.total_seconds() / 60.0
146 148
147 149 return 0
148 150
149 151 def write():
150 152 configuration = system_info.SysInfo(
151 153 system_info.rhodecode_config)()['value']
152 154 license_token = configuration['config']['license_token']
153 155
154 156 setup = dict(
155 157 workers=configuration['config']['server:main'].get(
156 158 'workers', '?'),
157 159 worker_type=configuration['config']['server:main'].get(
158 160 'worker_class', 'sync'),
159 161 )
160 162 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
161 163 del dbinfo['url']
162 164
163 165 metadata = dict(
164 166 desc='upgrade metadata info',
165 167 license_token=license_token,
166 168 created_on=datetime.datetime.utcnow().isoformat(),
167 169 usage=system_info.SysInfo(system_info.usage_info)()['value'],
168 170 platform=system_info.SysInfo(system_info.platform_type)()['value'],
169 171 database=dbinfo,
170 172 cpu=system_info.SysInfo(system_info.cpu)()['value'],
171 173 memory=system_info.SysInfo(system_info.memory)()['value'],
172 174 setup=setup
173 175 )
174 176
175 177 with open(metadata_destination, 'wb') as f:
176 178 f.write(ext_json.json.dumps(metadata))
177 179
178 180 settings = event.app.registry.settings
179 181 if settings.get('metadata.skip'):
180 182 return
181 183
182 184 # only write this every 24h, workers restart caused unwanted delays
183 185 try:
184 186 age_in_min = get_update_age()
185 187 except Exception:
186 188 age_in_min = 0
187 189
188 190 if age_in_min > 60 * 60 * 24:
189 191 return
190 192
191 193 try:
192 194 write()
193 195 except Exception:
194 196 pass
195 197
196 198
197 199 def write_usage_data(event):
198 200 import rhodecode
199 201 from rhodecode.lib import system_info
200 202 from rhodecode.lib import ext_json
201 203
202 204 settings = event.app.registry.settings
203 205 instance_tag = settings.get('metadata.write_usage_tag')
204 206 if not settings.get('metadata.write_usage'):
205 207 return
206 208
207 209 def get_update_age(dest_file):
208 210 now = datetime.datetime.now(datetime.UTC)
209 211
210 212 with open(dest_file, 'rb') as f:
211 213 data = ext_json.json.loads(f.read())
212 214 if 'created_on' in data:
213 215 update_date = parse(data['created_on'])
214 216 diff = now - update_date
215 217 return math.ceil(diff.total_seconds() / 60.0)
216 218
217 219 return 0
218 220
219 221 utc_date = datetime.datetime.now(datetime.UTC)
220 222 hour_quarter = int(math.ceil((utc_date.hour + utc_date.minute/60.0) / 6.))
221 223 fname = f'.rc_usage_{utc_date.year}{utc_date.month:02d}{utc_date.day:02d}_{hour_quarter}.json'
222 224 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
223 225
224 226 usage_dir = os.path.join(ini_loc, '.rcusage')
225 227 if not os.path.isdir(usage_dir):
226 228 os.makedirs(usage_dir)
227 229 usage_metadata_destination = os.path.join(usage_dir, fname)
228 230
229 231 try:
230 232 age_in_min = get_update_age(usage_metadata_destination)
231 233 except Exception:
232 234 age_in_min = 0
233 235
234 236 # write every 6th hour
235 237 if age_in_min and age_in_min < 60 * 6:
236 238 log.debug('Usage file created %s minutes ago, skipping (threshold: %s minutes)...',
237 239 age_in_min, 60 * 6)
238 240 return
239 241
240 242 def write(dest_file):
241 243 configuration = system_info.SysInfo(system_info.rhodecode_config)()['value']
242 244 license_token = configuration['config']['license_token']
243 245
244 246 metadata = dict(
245 247 desc='Usage data',
246 248 instance_tag=instance_tag,
247 249 license_token=license_token,
248 250 created_on=datetime.datetime.utcnow().isoformat(),
249 251 usage=system_info.SysInfo(system_info.usage_info)()['value'],
250 252 )
251 253
252 254 with open(dest_file, 'wb') as f:
253 255 f.write(ext_json.formatted_json(metadata))
254 256
255 257 try:
256 258 log.debug('Writing usage file at: %s', usage_metadata_destination)
257 259 write(usage_metadata_destination)
258 260 except Exception:
259 261 pass
260 262
261 263
262 264 def write_js_routes_if_enabled(event):
263 265 registry = event.app.registry
264 266
265 267 mapper = registry.queryUtility(IRoutesMapper)
266 268 _argument_prog = re.compile(r'\{(.*?)\}|:\((.*)\)')
267 269
268 270 def _extract_route_information(route):
269 271 """
270 272 Convert a route into tuple(name, path, args), eg:
271 273 ('show_user', '/profile/%(username)s', ['username'])
272 274 """
273 275
274 276 route_path = route.pattern
275 277 pattern = route.pattern
276 278
277 279 def replace(matchobj):
278 280 if matchobj.group(1):
279 281 return "%%(%s)s" % matchobj.group(1).split(':')[0]
280 282 else:
281 283 return "%%(%s)s" % matchobj.group(2)
282 284
283 285 route_path = _argument_prog.sub(replace, route_path)
284 286
285 287 if not route_path.startswith('/'):
286 288 route_path = f'/{route_path}'
287 289
288 290 return (
289 291 route.name,
290 292 route_path,
291 293 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
292 294 for arg in _argument_prog.findall(pattern)]
293 295 )
294 296
295 297 def get_routes():
296 298 # pyramid routes
297 299 for route in mapper.get_routes():
298 300 if not route.name.startswith('__'):
299 301 yield _extract_route_information(route)
300 302
301 303 if asbool(registry.settings.get('generate_js_files', 'false')):
302 304 static_path = AssetResolver().resolve('rhodecode:public').abspath()
303 305 jsroutes = get_routes()
304 306 jsroutes_file_content = generate_jsroutes_content(jsroutes)
305 307 jsroutes_file_path = os.path.join(
306 308 static_path, 'js', 'rhodecode', 'routes.js')
307 309
308 310 try:
309 311 with open(jsroutes_file_path, 'w', encoding='utf-8') as f:
310 312 f.write(jsroutes_file_content)
311 313 log.debug('generated JS files in %s', jsroutes_file_path)
312 314 except Exception:
313 315 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
314 316
315 317
316 318 def import_license_if_present(event):
317 319 """
318 320 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
319 321 does a import license key based on a presence of the file.
320 322 """
321 323 settings = event.app.registry.settings
322 324
325 rhodecode_edition_id = settings.get('rhodecode.edition_id')
323 326 license_file_path = settings.get('license.import_path')
324 327 force = settings.get('license.import_path_mode') == 'force'
325 if license_file_path:
328
329 if license_file_path and rhodecode_edition_id == 'EE':
330 log.debug('license.import_path= is set importing license from %s', license_file_path)
326 331 from rhodecode.model.meta import Session
327 332 from rhodecode.model.license import apply_license_from_file
333 try:
328 334 apply_license_from_file(license_file_path, force=force)
329 335 Session().commit()
336 except OSError:
337 log.exception('Failed to import license from %s, make sure this file exists', license_file_path)
330 338
331 339
332 340 class Subscriber(object):
333 341 """
334 342 Base class for subscribers to the pyramid event system.
335 343 """
336 344 def __call__(self, event):
337 345 self.run(event)
338 346
339 347 def run(self, event):
340 348 raise NotImplementedError('Subclass has to implement this.')
341 349
342 350
343 351 class AsyncSubscriber(Subscriber):
344 352 """
345 353 Subscriber that handles the execution of events in a separate task to not
346 354 block the execution of the code which triggers the event. It puts the
347 355 received events into a queue from which the worker process takes them in
348 356 order.
349 357 """
350 358 def __init__(self):
351 359 self._stop = False
352 360 self._eventq = queue.Queue()
353 361 self._worker = self.create_worker()
354 362 self._worker.start()
355 363
356 364 def __call__(self, event):
357 365 self._eventq.put(event)
358 366
359 367 def create_worker(self):
360 368 worker = Thread(target=self.do_work)
361 369 worker.daemon = True
362 370 return worker
363 371
364 372 def stop_worker(self):
365 373 self._stop = False
366 374 self._eventq.put(None)
367 375 self._worker.join()
368 376
369 377 def do_work(self):
370 378 while not self._stop:
371 379 event = self._eventq.get()
372 380 if event is not None:
373 381 self.run(event)
374 382
375 383
376 384 class AsyncSubprocessSubscriber(AsyncSubscriber):
377 385 """
378 386 Subscriber that uses the subprocess module to execute a command if an
379 387 event is received. Events are handled asynchronously::
380 388
381 389 subscriber = AsyncSubprocessSubscriber('ls -la', timeout=10)
382 390 subscriber(dummyEvent) # running __call__(event)
383 391
384 392 """
385 393
386 394 def __init__(self, cmd, timeout=None):
387 395 if not isinstance(cmd, (list, tuple)):
388 396 cmd = shlex.split(cmd)
389 397 super().__init__()
390 398 self._cmd = cmd
391 399 self._timeout = timeout
392 400
393 401 def run(self, event):
394 402 cmd = self._cmd
395 403 timeout = self._timeout
396 404 log.debug('Executing command %s.', cmd)
397 405
398 406 try:
399 407 output = subprocess.check_output(
400 408 cmd, timeout=timeout, stderr=subprocess.STDOUT)
401 409 log.debug('Command finished %s', cmd)
402 410 if output:
403 411 log.debug('Command output: %s', output)
404 412 except subprocess.TimeoutExpired as e:
405 413 log.exception('Timeout while executing command.')
406 414 if e.output:
407 415 log.error('Command output: %s', e.output)
408 416 except subprocess.CalledProcessError as e:
409 417 log.exception('Error while executing command.')
410 418 if e.output:
411 419 log.error('Command output: %s', e.output)
412 420 except Exception:
413 421 log.exception(
414 422 'Exception while executing command %s.', cmd)
General Comments 0
You need to be logged in to leave comments. Login now