Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_misc_app_manager/__init__.py @ 4270:0d7bb4df2343
Reformatted code base using black.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 19 Jun 2024 18:44:57 +0200 |
parents | 4aa62767f501 |
children |
comparison
equal
deleted
inserted
replaced
4269:64a85ce8be70 | 4270:0d7bb4df2343 |
---|---|
43 | 43 |
44 try: | 44 try: |
45 import yaml | 45 import yaml |
46 except ImportError: | 46 except ImportError: |
47 raise exceptions.MissingModule( | 47 raise exceptions.MissingModule( |
48 'Missing module PyYAML, please download/install it. You can use ' | 48 "Missing module PyYAML, please download/install it. You can use " |
49 '"pip install pyyaml"' | 49 '"pip install pyyaml"' |
50 ) | 50 ) |
51 | 51 |
52 try: | 52 try: |
53 from yaml import CLoader as Loader, CDumper as Dumper | 53 from yaml import CLoader as Loader, CDumper as Dumper |
55 log.warning( | 55 log.warning( |
56 "Can't use LibYAML binding (is libyaml installed?), pure Python version will be " | 56 "Can't use LibYAML binding (is libyaml installed?), pure Python version will be " |
57 "used, but it is slower" | 57 "used, but it is slower" |
58 ) | 58 ) |
59 from yaml import Loader, Dumper | 59 from yaml import Loader, Dumper |
60 | |
61 | |
62 | 60 |
63 | 61 |
64 PLUGIN_INFO = { | 62 PLUGIN_INFO = { |
65 C.PI_NAME: "Applications Manager", | 63 C.PI_NAME: "Applications Manager", |
66 C.PI_IMPORT_NAME: "APP_MANAGER", | 64 C.PI_IMPORT_NAME: "APP_MANAGER", |
71 C.PI_DESCRIPTION: _( | 69 C.PI_DESCRIPTION: _( |
72 """Applications Manager | 70 """Applications Manager |
73 | 71 |
74 Manage external applications using packagers, OS virtualization/containers or other | 72 Manage external applications using packagers, OS virtualization/containers or other |
75 software management tools. | 73 software management tools. |
76 """), | 74 """ |
75 ), | |
77 } | 76 } |
78 | 77 |
79 APP_FILE_PREFIX = "libervia_app_" | 78 APP_FILE_PREFIX = "libervia_app_" |
80 | 79 |
81 | 80 |
90 self._apps = {} | 89 self._apps = {} |
91 self._started = {} | 90 self._started = {} |
92 # instance id to app data map | 91 # instance id to app data map |
93 self._instances = {} | 92 self._instances = {} |
94 | 93 |
95 | |
96 self.persistent_data = persistent.LazyPersistentBinaryDict("app_manager") | 94 self.persistent_data = persistent.LazyPersistentBinaryDict("app_manager") |
97 | 95 |
98 host.bridge.add_method( | 96 host.bridge.add_method( |
99 "applications_list", | 97 "applications_list", |
100 ".plugin", | 98 ".plugin", |
126 method=self._get_exposed, | 124 method=self._get_exposed, |
127 async_=True, | 125 async_=True, |
128 ) | 126 ) |
129 # application has been started succeesfully, | 127 # application has been started succeesfully, |
130 # args: name, instance_id, extra | 128 # args: name, instance_id, extra |
131 host.bridge.add_signal( | 129 host.bridge.add_signal("application_started", ".plugin", signature="sss") |
132 "application_started", ".plugin", signature="sss" | |
133 ) | |
134 # application went wrong with the application | 130 # application went wrong with the application |
135 # args: name, instance_id, extra | 131 # args: name, instance_id, extra |
136 host.bridge.add_signal( | 132 host.bridge.add_signal("application_error", ".plugin", signature="sss") |
137 "application_error", ".plugin", signature="sss" | 133 yaml.add_constructor("!libervia_conf", self._libervia_conf_constr, Loader=Loader) |
138 ) | |
139 yaml.add_constructor( | 134 yaml.add_constructor( |
140 "!libervia_conf", self._libervia_conf_constr, Loader=Loader) | 135 "!libervia_generate_pwd", self._libervia_generate_pwd_constr, Loader=Loader |
136 ) | |
141 yaml.add_constructor( | 137 yaml.add_constructor( |
142 "!libervia_generate_pwd", self._libervia_generate_pwd_constr, Loader=Loader) | 138 "!libervia_param", self._libervia_param_constr, Loader=Loader |
143 yaml.add_constructor( | 139 ) |
144 "!libervia_param", self._libervia_param_constr, Loader=Loader) | |
145 | 140 |
146 def unload(self): | 141 def unload(self): |
147 log.debug("unloading applications manager") | 142 log.debug("unloading applications manager") |
148 for instances in self._started.values(): | 143 for instances in self._started.values(): |
149 for instance in instances: | 144 for instance in instances: |
150 data = instance['data'] | 145 data = instance["data"] |
151 if not data['single_instance']: | 146 if not data["single_instance"]: |
152 log.debug( | 147 log.debug( |
153 f"cleaning temporary directory at {data['_instance_dir_path']}") | 148 f"cleaning temporary directory at {data['_instance_dir_path']}" |
154 data['_instance_dir_obj'].cleanup() | 149 ) |
150 data["_instance_dir_obj"].cleanup() | |
155 | 151 |
156 def _libervia_conf_constr(self, loader, node) -> str: | 152 def _libervia_conf_constr(self, loader, node) -> str: |
157 """Get a value from Libervia configuration | 153 """Get a value from Libervia configuration |
158 | 154 |
159 A list is expected with either "name" of a config parameter, a one or more of | 155 A list is expected with either "name" of a config parameter, a one or more of |
181 "is expected" | 177 "is expected" |
182 ) | 178 ) |
183 | 179 |
184 value = self.host.memory.config_get(section, name, default) | 180 value = self.host.memory.config_get(section, name, default) |
185 # FIXME: "public_url" is used only here and doesn't take multi-sites into account | 181 # FIXME: "public_url" is used only here and doesn't take multi-sites into account |
186 if name == "public_url" and (not value or value.startswith('http')): | 182 if name == "public_url" and (not value or value.startswith("http")): |
187 if not value: | 183 if not value: |
188 log.warning(_( | 184 log.warning( |
189 'No value found for "public_url", using "example.org" for ' | 185 _( |
190 'now, please set the proper value in libervia.conf')) | 186 'No value found for "public_url", using "example.org" for ' |
187 "now, please set the proper value in libervia.conf" | |
188 ) | |
189 ) | |
191 else: | 190 else: |
192 log.warning(_( | 191 log.warning( |
193 'invalid value for "public_url" ({value}), it musts not start with ' | 192 _( |
194 'schema ("http"), ignoring it and using "example.org" ' | 193 'invalid value for "public_url" ({value}), it musts not start with ' |
195 'instead') | 194 'schema ("http"), ignoring it and using "example.org" ' |
196 .format(value=value)) | 195 "instead" |
196 ).format(value=value) | |
197 ) | |
197 value = "example.org" | 198 value = "example.org" |
198 | 199 |
199 if filter_ is None: | 200 if filter_ is None: |
200 pass | 201 pass |
201 elif filter_ == 'first': | 202 elif filter_ == "first": |
202 value = value[0] | 203 value = value[0] |
203 elif filter_ == "not": | 204 elif filter_ == "not": |
204 value = C.bool(value) | 205 value = C.bool(value) |
205 value = C.bool_const(not value).lower() | 206 value = C.bool_const(not value).lower() |
206 else: | 207 else: |
230 try: | 231 try: |
231 key = self._app_persistent_data[pwd_data_key] | 232 key = self._app_persistent_data[pwd_data_key] |
232 except KeyError: | 233 except KeyError: |
233 alphabet = string.ascii_letters + string.digits | 234 alphabet = string.ascii_letters + string.digits |
234 key_size = int(kwargs.get("size", 30)) | 235 key_size = int(kwargs.get("size", 30)) |
235 key = ''.join(secrets.choice(alphabet) for __ in range(key_size)) | 236 key = "".join(secrets.choice(alphabet) for __ in range(key_size)) |
236 self._app_persistent_data[pwd_data_key] = key | 237 self._app_persistent_data[pwd_data_key] = key |
237 else: | 238 else: |
238 log.debug(f"Re-using existing key for {name!r} password.") | 239 log.debug(f"Re-using existing key for {name!r} password.") |
239 return key | 240 return key |
240 | 241 |
253 | 254 |
254 def register(self, manager): | 255 def register(self, manager): |
255 name = manager.name | 256 name = manager.name |
256 if name in self._managers: | 257 if name in self._managers: |
257 raise exceptions.ConflictError( | 258 raise exceptions.ConflictError( |
258 f"There is already a manager with the name {name}") | 259 f"There is already a manager with the name {name}" |
260 ) | |
259 self._managers[manager.name] = manager | 261 self._managers[manager.name] = manager |
260 if manager.discover_path is not None: | 262 if manager.discover_path is not None: |
261 self.discover(manager.discover_path, manager) | 263 self.discover(manager.discover_path, manager) |
262 | 264 |
263 def get_manager(self, app_data: dict) -> AppManagerBackend: | 265 def get_manager(self, app_data: dict) -> AppManagerBackend: |
267 @raise exceptions.NotFound: manager is not registered | 269 @raise exceptions.NotFound: manager is not registered |
268 """ | 270 """ |
269 try: | 271 try: |
270 app_type = app_data["type"] | 272 app_type = app_data["type"] |
271 except KeyError: | 273 except KeyError: |
272 raise exceptions.DataError( | 274 raise exceptions.DataError('app file doesn\'t have the mandatory "type" key') |
273 "app file doesn't have the mandatory \"type\" key" | |
274 ) | |
275 if not isinstance(app_type, str): | 275 if not isinstance(app_type, str): |
276 raise exceptions.DataError( | 276 raise exceptions.DataError(f"invalid app data type: {app_type!r}") |
277 f"invalid app data type: {app_type!r}" | |
278 ) | |
279 app_type = app_type.strip() | 277 app_type = app_type.strip() |
280 try: | 278 try: |
281 return self._managers[app_type] | 279 return self._managers[app_type] |
282 except KeyError: | 280 except KeyError: |
283 raise exceptions.NotFound( | 281 raise exceptions.NotFound( |
284 f"No manager found to manage app of type {app_type!r}") | 282 f"No manager found to manage app of type {app_type!r}" |
285 | 283 ) |
286 def get_app_data( | 284 |
287 self, | 285 def get_app_data(self, id_type: Optional[str], identifier: str) -> dict: |
288 id_type: Optional[str], | |
289 identifier: str | |
290 ) -> dict: | |
291 """Retrieve instance's app_data from identifier | 286 """Retrieve instance's app_data from identifier |
292 | 287 |
293 @param id_type: type of the identifier, can be: | 288 @param id_type: type of the identifier, can be: |
294 - "name": identifier is a canonical application name | 289 - "name": identifier is a canonical application name |
295 the first found instance of this application is returned | 290 the first found instance of this application is returned |
298 @return: instance application data | 293 @return: instance application data |
299 @raise exceptions.NotFound: no instance with this id can be found | 294 @raise exceptions.NotFound: no instance with this id can be found |
300 @raise ValueError: id_type is invalid | 295 @raise ValueError: id_type is invalid |
301 """ | 296 """ |
302 if not id_type: | 297 if not id_type: |
303 id_type = 'name' | 298 id_type = "name" |
304 if id_type == 'name': | 299 if id_type == "name": |
305 identifier = identifier.lower().strip() | 300 identifier = identifier.lower().strip() |
306 try: | 301 try: |
307 return next(iter(self._started[identifier])) | 302 return next(iter(self._started[identifier])) |
308 except (KeyError, StopIteration): | 303 except (KeyError, StopIteration): |
309 raise exceptions.NotFound( | 304 raise exceptions.NotFound( |
310 f"No instance of {identifier!r} is currently running" | 305 f"No instance of {identifier!r} is currently running" |
311 ) | 306 ) |
312 elif id_type == 'instance': | 307 elif id_type == "instance": |
313 instance_id = identifier | 308 instance_id = identifier |
314 try: | 309 try: |
315 return self._instances[instance_id] | 310 return self._instances[instance_id] |
316 except KeyError: | 311 except KeyError: |
317 raise exceptions.NotFound( | 312 raise exceptions.NotFound( |
318 f"There is no application instance running with id {instance_id!r}" | 313 f"There is no application instance running with id {instance_id!r}" |
319 ) | 314 ) |
320 else: | 315 else: |
321 raise ValueError(f"invalid id_type: {id_type!r}") | 316 raise ValueError(f"invalid id_type: {id_type!r}") |
322 | 317 |
323 def discover( | 318 def discover(self, dir_path: Path, manager: AppManagerBackend | None = None) -> None: |
324 self, | |
325 dir_path: Path, | |
326 manager: AppManagerBackend|None = None | |
327 ) -> None: | |
328 """Search for app configuration file. | 319 """Search for app configuration file. |
329 | 320 |
330 App configuration files must start with [APP_FILE_PREFIX] and have a ``.yaml`` | 321 App configuration files must start with [APP_FILE_PREFIX] and have a ``.yaml`` |
331 extension. | 322 extension. |
332 """ | 323 """ |
334 if manager is None: | 325 if manager is None: |
335 try: | 326 try: |
336 app_data = self.parse(file_path) | 327 app_data = self.parse(file_path) |
337 manager = self.get_manager(app_data) | 328 manager = self.get_manager(app_data) |
338 except (exceptions.DataError, exceptions.NotFound) as e: | 329 except (exceptions.DataError, exceptions.NotFound) as e: |
339 log.warning( | 330 log.warning(f"Can't parse {file_path}, skipping: {e}") |
340 f"Can't parse {file_path}, skipping: {e}") | |
341 continue | 331 continue |
342 app_name = file_path.stem[len(APP_FILE_PREFIX):].strip().lower() | 332 app_name = file_path.stem[len(APP_FILE_PREFIX) :].strip().lower() |
343 if not app_name: | 333 if not app_name: |
344 log.warning(f"invalid app file name at {file_path}") | 334 log.warning(f"invalid app file name at {file_path}") |
345 continue | 335 continue |
346 app_dict = self._apps.setdefault(app_name, {}) | 336 app_dict = self._apps.setdefault(app_name, {}) |
347 manager_set = app_dict.setdefault(manager, set()) | 337 manager_set = app_dict.setdefault(manager, set()) |
348 manager_set.add(file_path) | 338 manager_set.add(file_path) |
349 log.debug( | 339 log.debug(f"{app_name!r} {manager.name} application found") |
350 f"{app_name!r} {manager.name} application found" | |
351 ) | |
352 | 340 |
353 def parse(self, file_path: Path, params: Optional[dict] = None) -> dict: | 341 def parse(self, file_path: Path, params: Optional[dict] = None) -> dict: |
354 """Parse Libervia application file | 342 """Parse Libervia application file |
355 | 343 |
356 @param params: parameters for running this instance | 344 @param params: parameters for running this instance |
365 app_data = self.load(f) | 353 app_data = self.load(f) |
366 self._params = None | 354 self._params = None |
367 if "name" not in app_data: | 355 if "name" not in app_data: |
368 # note that we don't use lower() here as we want human readable name and | 356 # note that we don't use lower() here as we want human readable name and |
369 # uppercase may be set on purpose | 357 # uppercase may be set on purpose |
370 app_data['name'] = file_path.stem[len(APP_FILE_PREFIX):].strip() | 358 app_data["name"] = file_path.stem[len(APP_FILE_PREFIX) :].strip() |
371 single_instance = app_data.setdefault("single_instance", True) | 359 single_instance = app_data.setdefault("single_instance", True) |
372 if not isinstance(single_instance, bool): | 360 if not isinstance(single_instance, bool): |
373 raise ValueError( | 361 raise ValueError( |
374 f'"single_instance" must be a boolean, but it is {type(single_instance)}' | 362 f'"single_instance" must be a boolean, but it is {type(single_instance)}' |
375 ) | 363 ) |
427 # to be defined in app_data allows explicitly it. | 415 # to be defined in app_data allows explicitly it. |
428 app_name = app_name.lower().strip() | 416 app_name = app_name.lower().strip() |
429 try: | 417 try: |
430 app_file_path = next(iter(next(iter(self._apps[app_name].values())))) | 418 app_file_path = next(iter(next(iter(self._apps[app_name].values())))) |
431 except KeyError: | 419 except KeyError: |
432 raise exceptions.NotFound( | 420 raise exceptions.NotFound(f"No application found with the name {app_name!r}") |
433 f"No application found with the name {app_name!r}" | |
434 ) | |
435 log.info(f"starting {app_name!r}") | 421 log.info(f"starting {app_name!r}") |
436 self._app_persistent_data = await self.persistent_data.get(app_name) or {} | 422 self._app_persistent_data = await self.persistent_data.get(app_name) or {} |
437 self._app_persistent_data["last_started"] = time.time() | 423 self._app_persistent_data["last_started"] = time.time() |
438 started_data = self._started.setdefault(app_name, []) | 424 started_data = self._started.setdefault(app_name, []) |
439 app_data = self.parse(app_file_path, extra) | 425 app_data = self.parse(app_file_path, extra) |
440 await self.persistent_data.aset(app_name, self._app_persistent_data) | 426 await self.persistent_data.aset(app_name, self._app_persistent_data) |
441 app_data["_started"] = False | 427 app_data["_started"] = False |
442 app_data['_file_path'] = app_file_path | 428 app_data["_file_path"] = app_file_path |
443 app_data['_name_canonical'] = app_name | 429 app_data["_name_canonical"] = app_name |
444 single_instance = app_data['single_instance'] | 430 single_instance = app_data["single_instance"] |
445 ret_data = { | 431 ret_data = {"name": app_name, "started": False} |
446 "name": app_name, | |
447 "started": False | |
448 } | |
449 if single_instance: | 432 if single_instance: |
450 if started_data: | 433 if started_data: |
451 instance_data = started_data[0] | 434 instance_data = started_data[0] |
452 instance_id = instance_data["_instance_id"] | 435 instance_id = instance_data["_instance_id"] |
453 ret_data["instance"] = instance_id | 436 ret_data["instance"] = instance_id |
460 else: | 443 else: |
461 cache_path = self.host.memory.get_cache_path( | 444 cache_path = self.host.memory.get_cache_path( |
462 PLUGIN_INFO[C.PI_IMPORT_NAME], app_name | 445 PLUGIN_INFO[C.PI_IMPORT_NAME], app_name |
463 ) | 446 ) |
464 cache_path.mkdir(0o700, parents=True, exist_ok=True) | 447 cache_path.mkdir(0o700, parents=True, exist_ok=True) |
465 app_data['_instance_dir_path'] = cache_path | 448 app_data["_instance_dir_path"] = cache_path |
466 else: | 449 else: |
467 dest_dir_obj = tempfile.TemporaryDirectory(prefix="libervia_app_") | 450 dest_dir_obj = tempfile.TemporaryDirectory(prefix="libervia_app_") |
468 app_data['_instance_dir_obj'] = dest_dir_obj | 451 app_data["_instance_dir_obj"] = dest_dir_obj |
469 app_data['_instance_dir_path'] = Path(dest_dir_obj.name) | 452 app_data["_instance_dir_path"] = Path(dest_dir_obj.name) |
470 instance_id = ret_data["instance"] = app_data['_instance_id'] = shortuuid.uuid() | 453 instance_id = ret_data["instance"] = app_data["_instance_id"] = shortuuid.uuid() |
471 manager = self.get_manager(app_data) | 454 manager = self.get_manager(app_data) |
472 app_data['_manager'] = manager | 455 app_data["_manager"] = manager |
473 started_data.append(app_data) | 456 started_data.append(app_data) |
474 self._instances[instance_id] = app_data | 457 self._instances[instance_id] = app_data |
475 # we retrieve exposed data such as url_prefix which can be useful computed exposed | 458 # we retrieve exposed data such as url_prefix which can be useful computed exposed |
476 # data must wait for the app to be started, so we skip them for now | 459 # data must wait for the app to be started, so we skip them for now |
477 ret_data["expose"] = await self.get_exposed( | 460 ret_data["expose"] = await self.get_exposed( |
480 | 463 |
481 try: | 464 try: |
482 start = manager.start | 465 start = manager.start |
483 except AttributeError: | 466 except AttributeError: |
484 raise exceptions.InternalError( | 467 raise exceptions.InternalError( |
485 f"{manager.name} doesn't have the mandatory \"start\" method" | 468 f'{manager.name} doesn\'t have the mandatory "start" method' |
486 ) | 469 ) |
487 else: | 470 else: |
488 defer.ensureDeferred(self.start_app(start, app_data)) | 471 defer.ensureDeferred(self.start_app(start, app_data)) |
489 return ret_data | 472 return ret_data |
490 | 473 |
496 except Exception as e: | 479 except Exception as e: |
497 log.exception(f"Can't start libervia app {app_name!r}") | 480 log.exception(f"Can't start libervia app {app_name!r}") |
498 self.host.bridge.application_error( | 481 self.host.bridge.application_error( |
499 app_name, | 482 app_name, |
500 instance_id, | 483 instance_id, |
501 data_format.serialise( | 484 data_format.serialise({"class": str(type(e)), "msg": str(e)}), |
502 { | 485 ) |
503 "class": str(type(e)), | |
504 "msg": str(e) | |
505 } | |
506 )) | |
507 else: | 486 else: |
508 app_data["_started"] = True | 487 app_data["_started"] = True |
509 self.host.bridge.application_started(app_name, instance_id, "") | 488 self.host.bridge.application_started(app_name, instance_id, "") |
510 log.info(f"{app_name!r} started") | 489 log.info(f"{app_name!r} started") |
511 | 490 |
512 def _stop(self, identifier, id_type, extra): | 491 def _stop(self, identifier, id_type, extra): |
513 extra = data_format.deserialise(extra) | 492 extra = data_format.deserialise(extra) |
514 return defer.ensureDeferred( | 493 return defer.ensureDeferred( |
515 self.stop(str(identifier), str(id_type) or None, extra)) | 494 self.stop(str(identifier), str(id_type) or None, extra) |
495 ) | |
516 | 496 |
517 async def stop( | 497 async def stop( |
518 self, | 498 self, |
519 identifier: str, | 499 identifier: str, |
520 id_type: Optional[str] = None, | 500 id_type: Optional[str] = None, |
525 | 505 |
526 app_data = self.get_app_data(id_type, identifier) | 506 app_data = self.get_app_data(id_type, identifier) |
527 | 507 |
528 log.info(f"stopping {app_data['name']!r}") | 508 log.info(f"stopping {app_data['name']!r}") |
529 | 509 |
530 app_name = app_data['_name_canonical'] | 510 app_name = app_data["_name_canonical"] |
531 instance_id = app_data['_instance_id'] | 511 instance_id = app_data["_instance_id"] |
532 manager = app_data['_manager'] | 512 manager = app_data["_manager"] |
533 | 513 |
534 try: | 514 try: |
535 stop = manager.stop | 515 stop = manager.stop |
536 except AttributeError: | 516 except AttributeError: |
537 raise exceptions.InternalError( | 517 raise exceptions.InternalError( |
538 f"{manager.name} doesn't have the mandatory \"stop\" method" | 518 f'{manager.name} doesn\'t have the mandatory "stop" method' |
539 ) | 519 ) |
540 else: | 520 else: |
541 try: | 521 try: |
542 await stop(app_data) | 522 await stop(app_data) |
543 except Exception as e: | 523 except Exception as e: |
549 | 529 |
550 try: | 530 try: |
551 del self._instances[instance_id] | 531 del self._instances[instance_id] |
552 except KeyError: | 532 except KeyError: |
553 log.error( | 533 log.error( |
554 f"INTERNAL ERROR: {instance_id!r} is not present in self._instances") | 534 f"INTERNAL ERROR: {instance_id!r} is not present in self._instances" |
535 ) | |
555 | 536 |
556 try: | 537 try: |
557 self._started[app_name].remove(app_data) | 538 self._started[app_name].remove(app_data) |
558 except ValueError: | 539 except ValueError: |
559 log.error( | 540 log.error( |
588 | 569 |
589 The manager's "compute_expose" method will be called if it exists. It can be used | 570 The manager's "compute_expose" method will be called if it exists. It can be used |
590 to handle manager specific conventions. | 571 to handle manager specific conventions. |
591 """ | 572 """ |
592 app_data = self.get_app_data(id_type, identifier) | 573 app_data = self.get_app_data(id_type, identifier) |
593 if app_data.get('_exposed_computed', False): | 574 if app_data.get("_exposed_computed", False): |
594 return app_data['expose'] | 575 return app_data["expose"] |
595 if extra is None: | 576 if extra is None: |
596 extra = {} | 577 extra = {} |
597 expose = app_data.setdefault("expose", {}) | 578 expose = app_data.setdefault("expose", {}) |
598 if "passwords" in expose: | 579 if "passwords" in expose: |
599 passwords = expose['passwords'] | 580 passwords = expose["passwords"] |
600 for name, value in list(passwords.items()): | 581 for name, value in list(passwords.items()): |
601 if isinstance(value, list): | 582 if isinstance(value, list): |
602 # if we have a list, is the sequence of keys leading to the value | 583 # if we have a list, is the sequence of keys leading to the value |
603 # to expose. | 584 # to expose. |
604 try: | 585 try: |
605 passwords[name] = self.get_app_data_value(value, app_data) | 586 passwords[name] = self.get_app_data_value(value, app_data) |
606 except KeyError: | 587 except KeyError: |
607 log.warning( | 588 log.warning( |
608 f"Can't retrieve exposed value for password {name!r}: {e}") | 589 f"Can't retrieve exposed value for password {name!r}: {e}" |
590 ) | |
609 del passwords[name] | 591 del passwords[name] |
610 | 592 |
611 for key in ("url_prefix", "front_url"): | 593 for key in ("url_prefix", "front_url"): |
612 value = expose.get(key) | 594 value = expose.get(key) |
613 if isinstance(value, list): | 595 if isinstance(value, list): |
614 try: | 596 try: |
615 expose[key] = self.get_app_data_value(value, app_data) | 597 expose[key] = self.get_app_data_value(value, app_data) |
616 except KeyError: | 598 except KeyError: |
617 log.warning( | 599 log.warning(f"Can't retrieve exposed value for {key!r} at {value}") |
618 f"Can't retrieve exposed value for {key!r} at {value}" | |
619 ) | |
620 del expose[key] | 600 del expose[key] |
621 | 601 |
622 front_url = expose.get("front_url") | 602 front_url = expose.get("front_url") |
623 if front_url: | 603 if front_url: |
624 # we want to be sure that a scheme is defined, defaulting to ``https`` | 604 # we want to be sure that a scheme is defined, defaulting to ``https`` |
626 if not parsed_url.scheme: | 606 if not parsed_url.scheme: |
627 if not parsed_url.netloc: | 607 if not parsed_url.netloc: |
628 path_elt = parsed_url.path.split("/", 1) | 608 path_elt = parsed_url.path.split("/", 1) |
629 parsed_url = parsed_url._replace( | 609 parsed_url = parsed_url._replace( |
630 netloc=path_elt[0], | 610 netloc=path_elt[0], |
631 path=f"/{path_elt[1]}" if len(path_elt) > 1 else "" | 611 path=f"/{path_elt[1]}" if len(path_elt) > 1 else "", |
632 ) | 612 ) |
633 parsed_url = parsed_url._replace(scheme='https') | 613 parsed_url = parsed_url._replace(scheme="https") |
634 expose["front_url"] = urlunparse(parsed_url) | 614 expose["front_url"] = urlunparse(parsed_url) |
635 | 615 |
636 if extra.get("skip_compute", False): | 616 if extra.get("skip_compute", False): |
637 return expose | 617 return expose |
638 | 618 |
639 try: | 619 try: |
640 compute_expose = app_data['_manager'].compute_expose | 620 compute_expose = app_data["_manager"].compute_expose |
641 except AttributeError: | 621 except AttributeError: |
642 pass | 622 pass |
643 else: | 623 else: |
644 await compute_expose(app_data) | 624 await compute_expose(app_data) |
645 | 625 |
646 app_data['_exposed_computed'] = True | 626 app_data["_exposed_computed"] = True |
647 return expose | 627 return expose |
648 | 628 |
649 async def _do_prepare( | 629 async def _do_prepare( |
650 self, | 630 self, |
651 app_data: dict, | 631 app_data: dict, |
652 ) -> None: | 632 ) -> None: |
653 name = app_data['name'] | 633 name = app_data["name"] |
654 dest_path = app_data['_instance_dir_path'] | 634 dest_path = app_data["_instance_dir_path"] |
655 if next(dest_path.iterdir(), None) != None: | 635 if next(dest_path.iterdir(), None) != None: |
656 log.debug(f"There is already a prepared dir at {dest_path}, nothing to do") | 636 log.debug(f"There is already a prepared dir at {dest_path}, nothing to do") |
657 return | 637 return |
658 try: | 638 try: |
659 prepare = app_data['prepare'].copy() | 639 prepare = app_data["prepare"].copy() |
660 except KeyError: | 640 except KeyError: |
661 prepare = {} | 641 prepare = {} |
662 | 642 |
663 if not prepare: | 643 if not prepare: |
664 log.debug(f"Nothing to prepare for {name!r}") | 644 log.debug(f"Nothing to prepare for {name!r}") |
666 | 646 |
667 for action, value in list(prepare.items()): | 647 for action, value in list(prepare.items()): |
668 log.debug(f"[{name}] [prepare] running {action!r} action") | 648 log.debug(f"[{name}] [prepare] running {action!r} action") |
669 if action == "git": | 649 if action == "git": |
670 try: | 650 try: |
671 git_path = which('git')[0] | 651 git_path = which("git")[0] |
672 except IndexError: | 652 except IndexError: |
673 raise exceptions.NotFound( | 653 raise exceptions.NotFound( |
674 "Can't find \"git\" executable, {name} can't be started without it" | 654 "Can't find \"git\" executable, {name} can't be started without it" |
675 ) | 655 ) |
676 await async_process.run(git_path, "clone", value, str(dest_path)) | 656 await async_process.run(git_path, "clone", value, str(dest_path)) |
686 | 666 |
687 async def _do_create_files( | 667 async def _do_create_files( |
688 self, | 668 self, |
689 app_data: dict, | 669 app_data: dict, |
690 ) -> None: | 670 ) -> None: |
691 dest_path = app_data['_instance_dir_path'] | 671 dest_path = app_data["_instance_dir_path"] |
692 files = app_data.get('files') | 672 files = app_data.get("files") |
693 if not files: | 673 if not files: |
694 return | 674 return |
695 if not isinstance(files, dict): | 675 if not isinstance(files, dict): |
696 raise ValueError('"files" must be a dictionary') | 676 raise ValueError('"files" must be a dictionary') |
697 for filename, data in files.items(): | 677 for filename, data in files.items(): |