##// END OF EJS Templates
feat(license): allow import of license from rhodecode_enterprise.license which got broken after python3 migration
super-admin -
r5530:6b2825dc default
parent child Browse files
Show More
@@ -0,0 +1,17 b''
1
2 def apply_license(*args, **kwargs):
3 pass
4
5 try:
6 from rc_license.models import apply_license
7 except ImportError:
8 pass
9
10
11 def apply_license_from_file(*args, **kwargs):
12 pass
13
14 try:
15 from rc_license.models import apply_license_from_file
16 except ImportError:
17 pass
@@ -1,124 +1,123 b''
1 1 # Copyright (C) 2016-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 logging
19 19
20 20 import click
21 21 import pyramid.paster
22 22
23 23 from rhodecode.lib.pyramid_utils import bootstrap
24 24 from rhodecode.lib.config_utils import get_app_config
25 25 from rhodecode.lib.db_manage import DbManage
26 26 from rhodecode.lib.utils2 import get_encryption_key
27 27 from rhodecode.model.db import Session
28 28
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 @click.command()
34 34 @click.argument('ini_path', type=click.Path(exists=True))
35 35 @click.option(
36 36 '--force-yes/--force-no', default=None,
37 37 help="Force yes/no to every question")
38 38 @click.option(
39 39 '--user',
40 40 default=None,
41 41 help='Initial super-admin username')
42 42 @click.option(
43 43 '--email',
44 44 default=None,
45 45 help='Initial super-admin email address.')
46 46 @click.option(
47 47 '--password',
48 48 default=None,
49 49 help='Initial super-admin password. Minimum 6 chars.')
50 50 @click.option(
51 51 '--api-key',
52 52 help='Initial API key for the admin user')
53 53 @click.option(
54 54 '--repos',
55 55 default=None,
56 56 help='Absolute path to storage location. This is storage for all '
57 57 'existing and future repositories, and repository groups.')
58 58 @click.option(
59 59 '--public-access/--no-public-access',
60 60 default=None,
61 61 help='Enable public access on this installation. '
62 62 'Default is public access enabled.')
63 63 @click.option(
64 64 '--skip-existing-db',
65 65 default=False,
66 66 is_flag=True,
67 67 help='Do not destroy and re-initialize the database if it already exist.')
68 68 @click.option(
69 69 '--apply-license-key',
70 70 default=False,
71 71 is_flag=True,
72 72 help='Get the license key from a license file or ENV and apply during DB creation.')
73 73 def main(ini_path, force_yes, user, email, password, api_key, repos,
74 74 public_access, skip_existing_db, apply_license_key):
75 75 return command(ini_path, force_yes, user, email, password, api_key,
76 76 repos, public_access, skip_existing_db, apply_license_key)
77 77
78 78
79 79 def command(ini_path, force_yes, user, email, password, api_key, repos,
80 80 public_access, skip_existing_db, apply_license_key):
81 81 # mapping of old parameters to new CLI from click
82 82 options = dict(
83 83 username=user,
84 84 email=email,
85 85 password=password,
86 86 api_key=api_key,
87 87 repos_location=repos,
88 88 force_ask=force_yes,
89 89 public_access=public_access
90 90 )
91 91 pyramid.paster.setup_logging(ini_path)
92 92
93 93 config = get_app_config(ini_path)
94 94
95 95 db_uri = config['sqlalchemy.db1.url']
96 96 enc_key = get_encryption_key(config)
97 97 dbmanage = DbManage(log_sql=True, dbconf=db_uri, root='.',
98 98 tests=False, cli_args=options, enc_key=enc_key)
99 99 if skip_existing_db and dbmanage.db_exists():
100 100 return
101 101
102 102 dbmanage.create_tables(override=True)
103 103 dbmanage.set_db_version()
104 104 opts = dbmanage.config_prompt(None)
105 105 dbmanage.create_settings(opts)
106 106 dbmanage.create_default_user()
107 107 dbmanage.create_admin_and_prompt()
108 108 dbmanage.create_permissions()
109 109 dbmanage.populate_default_permissions()
110 110 if apply_license_key:
111 try:
112 from rc_license.models import apply_trial_license_if_missing
113 apply_trial_license_if_missing(force=True)
114 except ImportError:
115 pass
111 from rhodecode.model.license import apply_license_from_file
112 license_file_path = config.get('license.import_path')
113 if license_file_path:
114 apply_license_from_file(license_file_path, force=True)
116 115
117 116 Session().commit()
118 117
119 118 with bootstrap(ini_path, env={'RC_CMD_SETUP_RC': '1'}) as env:
120 119 msg = 'Successfully initialized database, schema and default data.'
121 120 print()
122 121 print('*' * len(msg))
123 122 print(msg.upper())
124 123 print('*' * len(msg))
@@ -1,399 +1,414 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 settings = event.app.registry.settings
115 115 vcs_server_enabled = settings['vcs.server.enable']
116 116 import_on_startup = settings['startup.import_repos']
117 117 if vcs_server_enabled and import_on_startup:
118 118 from rhodecode.model.scm import ScmModel
119 119 from rhodecode.lib.utils import repo2db_mapper
120 120 scm = ScmModel()
121 121 repositories = scm.repo_scan(scm.repos_path)
122 122 repo2db_mapper(repositories, remove_obsolete=False)
123 123
124 124
125 125 def write_metadata_if_needed(event):
126 126 """
127 127 Writes upgrade metadata
128 128 """
129 129 import rhodecode
130 130 from rhodecode.lib import system_info
131 131 from rhodecode.lib import ext_json
132 132
133 133 fname = '.rcmetadata.json'
134 134 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
135 135 metadata_destination = os.path.join(ini_loc, fname)
136 136
137 137 def get_update_age():
138 138 now = datetime.datetime.utcnow()
139 139
140 140 with open(metadata_destination, 'rb') as f:
141 141 data = ext_json.json.loads(f.read())
142 142 if 'created_on' in data:
143 143 update_date = parse(data['created_on'])
144 144 diff = now - update_date
145 145 return diff.total_seconds() / 60.0
146 146
147 147 return 0
148 148
149 149 def write():
150 150 configuration = system_info.SysInfo(
151 151 system_info.rhodecode_config)()['value']
152 152 license_token = configuration['config']['license_token']
153 153
154 154 setup = dict(
155 155 workers=configuration['config']['server:main'].get(
156 156 'workers', '?'),
157 157 worker_type=configuration['config']['server:main'].get(
158 158 'worker_class', 'sync'),
159 159 )
160 160 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
161 161 del dbinfo['url']
162 162
163 163 metadata = dict(
164 164 desc='upgrade metadata info',
165 165 license_token=license_token,
166 166 created_on=datetime.datetime.utcnow().isoformat(),
167 167 usage=system_info.SysInfo(system_info.usage_info)()['value'],
168 168 platform=system_info.SysInfo(system_info.platform_type)()['value'],
169 169 database=dbinfo,
170 170 cpu=system_info.SysInfo(system_info.cpu)()['value'],
171 171 memory=system_info.SysInfo(system_info.memory)()['value'],
172 172 setup=setup
173 173 )
174 174
175 175 with open(metadata_destination, 'wb') as f:
176 176 f.write(ext_json.json.dumps(metadata))
177 177
178 178 settings = event.app.registry.settings
179 179 if settings.get('metadata.skip'):
180 180 return
181 181
182 182 # only write this every 24h, workers restart caused unwanted delays
183 183 try:
184 184 age_in_min = get_update_age()
185 185 except Exception:
186 186 age_in_min = 0
187 187
188 188 if age_in_min > 60 * 60 * 24:
189 189 return
190 190
191 191 try:
192 192 write()
193 193 except Exception:
194 194 pass
195 195
196 196
197 197 def write_usage_data(event):
198 198 import rhodecode
199 199 from rhodecode.lib import system_info
200 200 from rhodecode.lib import ext_json
201 201
202 202 settings = event.app.registry.settings
203 203 instance_tag = settings.get('metadata.write_usage_tag')
204 204 if not settings.get('metadata.write_usage'):
205 205 return
206 206
207 207 def get_update_age(dest_file):
208 now = datetime.datetime.utcnow()
208 now = datetime.datetime.now(datetime.UTC)
209 209
210 210 with open(dest_file, 'rb') as f:
211 211 data = ext_json.json.loads(f.read())
212 212 if 'created_on' in data:
213 213 update_date = parse(data['created_on'])
214 214 diff = now - update_date
215 215 return math.ceil(diff.total_seconds() / 60.0)
216 216
217 217 return 0
218 218
219 utc_date = datetime.datetime.utcnow()
219 utc_date = datetime.datetime.now(datetime.UTC)
220 220 hour_quarter = int(math.ceil((utc_date.hour + utc_date.minute/60.0) / 6.))
221 fname = '.rc_usage_{date.year}{date.month:02d}{date.day:02d}_{hour}.json'.format(
222 date=utc_date, hour=hour_quarter)
221 fname = f'.rc_usage_{utc_date.year}{utc_date.month:02d}{utc_date.day:02d}_{hour_quarter}.json'
223 222 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
224 223
225 224 usage_dir = os.path.join(ini_loc, '.rcusage')
226 225 if not os.path.isdir(usage_dir):
227 226 os.makedirs(usage_dir)
228 227 usage_metadata_destination = os.path.join(usage_dir, fname)
229 228
230 229 try:
231 230 age_in_min = get_update_age(usage_metadata_destination)
232 231 except Exception:
233 232 age_in_min = 0
234 233
235 234 # write every 6th hour
236 235 if age_in_min and age_in_min < 60 * 6:
237 236 log.debug('Usage file created %s minutes ago, skipping (threshold: %s minutes)...',
238 237 age_in_min, 60 * 6)
239 238 return
240 239
241 240 def write(dest_file):
242 241 configuration = system_info.SysInfo(system_info.rhodecode_config)()['value']
243 242 license_token = configuration['config']['license_token']
244 243
245 244 metadata = dict(
246 245 desc='Usage data',
247 246 instance_tag=instance_tag,
248 247 license_token=license_token,
249 248 created_on=datetime.datetime.utcnow().isoformat(),
250 249 usage=system_info.SysInfo(system_info.usage_info)()['value'],
251 250 )
252 251
253 252 with open(dest_file, 'wb') as f:
254 253 f.write(ext_json.formatted_json(metadata))
255 254
256 255 try:
257 256 log.debug('Writing usage file at: %s', usage_metadata_destination)
258 257 write(usage_metadata_destination)
259 258 except Exception:
260 259 pass
261 260
262 261
263 262 def write_js_routes_if_enabled(event):
264 263 registry = event.app.registry
265 264
266 265 mapper = registry.queryUtility(IRoutesMapper)
267 266 _argument_prog = re.compile(r'\{(.*?)\}|:\((.*)\)')
268 267
269 268 def _extract_route_information(route):
270 269 """
271 270 Convert a route into tuple(name, path, args), eg:
272 271 ('show_user', '/profile/%(username)s', ['username'])
273 272 """
274 273
275 274 route_path = route.pattern
276 275 pattern = route.pattern
277 276
278 277 def replace(matchobj):
279 278 if matchobj.group(1):
280 279 return "%%(%s)s" % matchobj.group(1).split(':')[0]
281 280 else:
282 281 return "%%(%s)s" % matchobj.group(2)
283 282
284 283 route_path = _argument_prog.sub(replace, route_path)
285 284
286 285 if not route_path.startswith('/'):
287 286 route_path = f'/{route_path}'
288 287
289 288 return (
290 289 route.name,
291 290 route_path,
292 291 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
293 292 for arg in _argument_prog.findall(pattern)]
294 293 )
295 294
296 295 def get_routes():
297 296 # pyramid routes
298 297 for route in mapper.get_routes():
299 298 if not route.name.startswith('__'):
300 299 yield _extract_route_information(route)
301 300
302 301 if asbool(registry.settings.get('generate_js_files', 'false')):
303 302 static_path = AssetResolver().resolve('rhodecode:public').abspath()
304 303 jsroutes = get_routes()
305 304 jsroutes_file_content = generate_jsroutes_content(jsroutes)
306 305 jsroutes_file_path = os.path.join(
307 306 static_path, 'js', 'rhodecode', 'routes.js')
308 307
309 308 try:
310 309 with open(jsroutes_file_path, 'w', encoding='utf-8') as f:
311 310 f.write(jsroutes_file_content)
312 311 log.debug('generated JS files in %s', jsroutes_file_path)
313 312 except Exception:
314 313 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
315 314
316 315
316 def import_license_if_present(event):
317 """
318 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
319 does a import license key based on a presence of the file.
320 """
321 settings = event.app.registry.settings
322
323 license_file_path = settings.get('license.import_path')
324 force = settings.get('license.import_path_mode') == 'force'
325 if license_file_path:
326 from rhodecode.model.meta import Session
327 from rhodecode.model.license import apply_license_from_file
328 apply_license_from_file(license_file_path, force=force)
329 Session().commit()
330
331
317 332 class Subscriber(object):
318 333 """
319 334 Base class for subscribers to the pyramid event system.
320 335 """
321 336 def __call__(self, event):
322 337 self.run(event)
323 338
324 339 def run(self, event):
325 340 raise NotImplementedError('Subclass has to implement this.')
326 341
327 342
328 343 class AsyncSubscriber(Subscriber):
329 344 """
330 345 Subscriber that handles the execution of events in a separate task to not
331 346 block the execution of the code which triggers the event. It puts the
332 347 received events into a queue from which the worker process takes them in
333 348 order.
334 349 """
335 350 def __init__(self):
336 351 self._stop = False
337 352 self._eventq = queue.Queue()
338 353 self._worker = self.create_worker()
339 354 self._worker.start()
340 355
341 356 def __call__(self, event):
342 357 self._eventq.put(event)
343 358
344 359 def create_worker(self):
345 360 worker = Thread(target=self.do_work)
346 361 worker.daemon = True
347 362 return worker
348 363
349 364 def stop_worker(self):
350 365 self._stop = False
351 366 self._eventq.put(None)
352 367 self._worker.join()
353 368
354 369 def do_work(self):
355 370 while not self._stop:
356 371 event = self._eventq.get()
357 372 if event is not None:
358 373 self.run(event)
359 374
360 375
361 376 class AsyncSubprocessSubscriber(AsyncSubscriber):
362 377 """
363 378 Subscriber that uses the subprocess module to execute a command if an
364 379 event is received. Events are handled asynchronously::
365 380
366 381 subscriber = AsyncSubprocessSubscriber('ls -la', timeout=10)
367 382 subscriber(dummyEvent) # running __call__(event)
368 383
369 384 """
370 385
371 386 def __init__(self, cmd, timeout=None):
372 387 if not isinstance(cmd, (list, tuple)):
373 388 cmd = shlex.split(cmd)
374 389 super().__init__()
375 390 self._cmd = cmd
376 391 self._timeout = timeout
377 392
378 393 def run(self, event):
379 394 cmd = self._cmd
380 395 timeout = self._timeout
381 396 log.debug('Executing command %s.', cmd)
382 397
383 398 try:
384 399 output = subprocess.check_output(
385 400 cmd, timeout=timeout, stderr=subprocess.STDOUT)
386 401 log.debug('Command finished %s', cmd)
387 402 if output:
388 403 log.debug('Command output: %s', output)
389 404 except subprocess.TimeoutExpired as e:
390 405 log.exception('Timeout while executing command.')
391 406 if e.output:
392 407 log.error('Command output: %s', e.output)
393 408 except subprocess.CalledProcessError as e:
394 409 log.exception('Error while executing command.')
395 410 if e.output:
396 411 log.error('Command output: %s', e.output)
397 412 except Exception:
398 413 log.exception(
399 414 'Exception while executing command %s.', cmd)
General Comments 0
You need to be logged in to leave comments. Login now