Mercurial > libervia-backend
annotate libervia/backend/plugins/plugin_misc_app_manager/__init__.py @ 4309:b56b1eae7994
component email gateway: add multicasting:
XEP-0033 multicasting is now supported both for incoming and outgoing messages. XEP-0033
metadata are converted to suitable Email headers and vice versa.
Email address and JID are both supported, and delivery is done by the gateway when
suitable on incoming messages.
rel 450
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 26 Sep 2024 16:12:01 +0200 |
parents | 0d7bb4df2343 |
children |
rev | line source |
---|---|
3372 | 1 #!/usr/bin/env python3 |
2 | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
3 # Libervia plugin to manage external applications |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
4 # Copyright (C) 2009-2024 Jérôme Poisson (goffi@goffi.org) |
3372 | 5 |
6 # This program is free software: you can redistribute it and/or modify | |
7 # it under the terms of the GNU Affero General Public License as published by | |
8 # the Free Software Foundation, either version 3 of the License, or | |
9 # (at your option) any later version. | |
10 | |
11 # This program is distributed in the hope that it will be useful, | |
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 # GNU Affero General Public License for more details. | |
15 | |
16 # You should have received a copy of the GNU Affero General Public License | |
17 # along with this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
19 from functools import partial, reduce |
3372 | 20 from pathlib import Path |
21 import secrets | |
22 import string | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
23 import tempfile |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
24 import time |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
25 from typing import Any, Callable, List, Optional |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
26 from urllib.parse import urlparse, urlunparse |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
27 |
3372 | 28 import shortuuid |
29 from twisted.internet import defer | |
30 from twisted.python.procutils import which | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
31 from yaml.constructor import ConstructorError |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
32 |
4071
4b842c1fb686
refactoring: renamed `sat` package to `libervia.backend`
Goffi <goffi@goffi.org>
parents:
4037
diff
changeset
|
33 from libervia.backend.core import exceptions |
4b842c1fb686
refactoring: renamed `sat` package to `libervia.backend`
Goffi <goffi@goffi.org>
parents:
4037
diff
changeset
|
34 from libervia.backend.core.constants import Const as C |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
35 from libervia.backend.core.i18n import _ |
4071
4b842c1fb686
refactoring: renamed `sat` package to `libervia.backend`
Goffi <goffi@goffi.org>
parents:
4037
diff
changeset
|
36 from libervia.backend.core.log import getLogger |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
37 from libervia.backend.memory import persistent |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
38 from libervia.backend.plugins.plugin_misc_app_manager.models import AppManagerBackend |
4071
4b842c1fb686
refactoring: renamed `sat` package to `libervia.backend`
Goffi <goffi@goffi.org>
parents:
4037
diff
changeset
|
39 from libervia.backend.tools.common import data_format |
4b842c1fb686
refactoring: renamed `sat` package to `libervia.backend`
Goffi <goffi@goffi.org>
parents:
4037
diff
changeset
|
40 from libervia.backend.tools.common import async_process |
3372 | 41 |
42 log = getLogger(__name__) | |
43 | |
44 try: | |
45 import yaml | |
46 except ImportError: | |
47 raise exceptions.MissingModule( | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
48 "Missing module PyYAML, please download/install it. You can use " |
3372 | 49 '"pip install pyyaml"' |
50 ) | |
51 | |
52 try: | |
53 from yaml import CLoader as Loader, CDumper as Dumper | |
54 except ImportError: | |
55 log.warning( | |
56 "Can't use LibYAML binding (is libyaml installed?), pure Python version will be " | |
57 "used, but it is slower" | |
58 ) | |
59 from yaml import Loader, Dumper | |
60 | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
61 |
3372 | 62 PLUGIN_INFO = { |
63 C.PI_NAME: "Applications Manager", | |
64 C.PI_IMPORT_NAME: "APP_MANAGER", | |
65 C.PI_TYPE: C.PLUG_TYPE_MISC, | |
66 C.PI_MODES: C.PLUG_MODE_BOTH, | |
67 C.PI_MAIN: "AppManager", | |
68 C.PI_HANDLER: "no", | |
69 C.PI_DESCRIPTION: _( | |
70 """Applications Manager | |
71 | |
72 Manage external applications using packagers, OS virtualization/containers or other | |
73 software management tools. | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
74 """ |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
75 ), |
3372 | 76 } |
77 | |
4085
c93b02000ae4
plugin app manager: rename `sat` to `libervia`
Goffi <goffi@goffi.org>
parents:
4071
diff
changeset
|
78 APP_FILE_PREFIX = "libervia_app_" |
3372 | 79 |
80 | |
81 class AppManager: | |
82 load = partial(yaml.load, Loader=Loader) | |
83 dump = partial(yaml.dump, Dumper=Dumper) | |
84 | |
85 def __init__(self, host): | |
86 log.info(_("plugin Applications Manager initialization")) | |
87 self.host = host | |
88 self._managers = {} | |
89 self._apps = {} | |
90 self._started = {} | |
91 # instance id to app data map | |
92 self._instances = {} | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
93 |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
94 self.persistent_data = persistent.LazyPersistentBinaryDict("app_manager") |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
95 |
4037
524856bd7b19
massive refactoring to switch from camelCase to snake_case:
Goffi <goffi@goffi.org>
parents:
3998
diff
changeset
|
96 host.bridge.add_method( |
524856bd7b19
massive refactoring to switch from camelCase to snake_case:
Goffi <goffi@goffi.org>
parents:
3998
diff
changeset
|
97 "applications_list", |
3372 | 98 ".plugin", |
99 in_sign="as", | |
100 out_sign="as", | |
101 method=self.list_applications, | |
102 ) | |
4037
524856bd7b19
massive refactoring to switch from camelCase to snake_case:
Goffi <goffi@goffi.org>
parents:
3998
diff
changeset
|
103 host.bridge.add_method( |
524856bd7b19
massive refactoring to switch from camelCase to snake_case:
Goffi <goffi@goffi.org>
parents:
3998
diff
changeset
|
104 "application_start", |
3372 | 105 ".plugin", |
106 in_sign="ss", | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
107 out_sign="s", |
3372 | 108 method=self._start, |
109 async_=True, | |
110 ) | |
4037
524856bd7b19
massive refactoring to switch from camelCase to snake_case:
Goffi <goffi@goffi.org>
parents:
3998
diff
changeset
|
111 host.bridge.add_method( |
524856bd7b19
massive refactoring to switch from camelCase to snake_case:
Goffi <goffi@goffi.org>
parents:
3998
diff
changeset
|
112 "application_stop", |
3372 | 113 ".plugin", |
114 in_sign="sss", | |
115 out_sign="", | |
116 method=self._stop, | |
117 async_=True, | |
118 ) | |
4037
524856bd7b19
massive refactoring to switch from camelCase to snake_case:
Goffi <goffi@goffi.org>
parents:
3998
diff
changeset
|
119 host.bridge.add_method( |
524856bd7b19
massive refactoring to switch from camelCase to snake_case:
Goffi <goffi@goffi.org>
parents:
3998
diff
changeset
|
120 "application_exposed_get", |
3372 | 121 ".plugin", |
122 in_sign="sss", | |
123 out_sign="s", | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
124 method=self._get_exposed, |
3372 | 125 async_=True, |
126 ) | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
127 # application has been started succeesfully, |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
128 # args: name, instance_id, extra |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
129 host.bridge.add_signal("application_started", ".plugin", signature="sss") |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
130 # application went wrong with the application |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
131 # args: name, instance_id, extra |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
132 host.bridge.add_signal("application_error", ".plugin", signature="sss") |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
133 yaml.add_constructor("!libervia_conf", self._libervia_conf_constr, Loader=Loader) |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
134 yaml.add_constructor( |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
135 "!libervia_generate_pwd", self._libervia_generate_pwd_constr, Loader=Loader |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
136 ) |
3372 | 137 yaml.add_constructor( |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
138 "!libervia_param", self._libervia_param_constr, Loader=Loader |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
139 ) |
3372 | 140 |
141 def unload(self): | |
142 log.debug("unloading applications manager") | |
143 for instances in self._started.values(): | |
144 for instance in instances: | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
145 data = instance["data"] |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
146 if not data["single_instance"]: |
3372 | 147 log.debug( |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
148 f"cleaning temporary directory at {data['_instance_dir_path']}" |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
149 ) |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
150 data["_instance_dir_obj"].cleanup() |
3372 | 151 |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
152 def _libervia_conf_constr(self, loader, node) -> str: |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
153 """Get a value from Libervia configuration |
3372 | 154 |
155 A list is expected with either "name" of a config parameter, a one or more of | |
156 those parameters: | |
157 - section | |
158 - name | |
159 - default value | |
160 - filter | |
161 filter can be: | |
162 - "first": get the first item of the value | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
163 - "not": get the opposite value (to be used with booleans) |
3372 | 164 """ |
165 config_data = loader.construct_sequence(node) | |
166 if len(config_data) == 1: | |
167 section, name, default, filter_ = "", config_data[0], None, None | |
168 if len(config_data) == 2: | |
169 (section, name), default, filter_ = config_data, None, None | |
170 elif len(config_data) == 3: | |
171 (section, name, default), filter_ = config_data, None | |
172 elif len(config_data) == 4: | |
173 section, name, default, filter_ = config_data | |
174 else: | |
175 raise ValueError( | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
176 f"invalid !libervia_conf value ({config_data!r}), a list of 1 to 4 items " |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
177 "is expected" |
3372 | 178 ) |
179 | |
4037
524856bd7b19
massive refactoring to switch from camelCase to snake_case:
Goffi <goffi@goffi.org>
parents:
3998
diff
changeset
|
180 value = self.host.memory.config_get(section, name, default) |
3564
2c9e95796371
plugin app manager: "public_url" must NOT have a schema
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
181 # FIXME: "public_url" is used only here and doesn't take multi-sites into account |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
182 if name == "public_url" and (not value or value.startswith("http")): |
3372 | 183 if not value: |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
184 log.warning( |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
185 _( |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
186 'No value found for "public_url", using "example.org" for ' |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
187 "now, please set the proper value in libervia.conf" |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
188 ) |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
189 ) |
3372 | 190 else: |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
191 log.warning( |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
192 _( |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
193 'invalid value for "public_url" ({value}), it musts not start with ' |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
194 'schema ("http"), ignoring it and using "example.org" ' |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
195 "instead" |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
196 ).format(value=value) |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
197 ) |
3564
2c9e95796371
plugin app manager: "public_url" must NOT have a schema
Goffi <goffi@goffi.org>
parents:
3479
diff
changeset
|
198 value = "example.org" |
3372 | 199 |
200 if filter_ is None: | |
201 pass | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
202 elif filter_ == "first": |
3372 | 203 value = value[0] |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
204 elif filter_ == "not": |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
205 value = C.bool(value) |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
206 value = C.bool_const(not value).lower() |
3372 | 207 else: |
208 raise ValueError(f"unmanaged filter: {filter_}") | |
209 | |
210 return value | |
211 | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
212 def _libervia_generate_pwd_constr(self, loader, node) -> str: |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
213 """Generate a password and store it in persistent data. |
3372 | 214 |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
215 If the password has already been generated previously, it is reused. |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
216 Mapping arguments are: |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
217 ``name`` (str) **required** |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
218 Name of the password. Will be used to store it. |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
219 ``size`` (int) |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
220 Size of the password to generate. Default to 30 |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
221 """ |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
222 try: |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
223 kwargs = loader.construct_mapping(node) |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
224 name = kwargs["name"] |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
225 except (ConstructorError, KeyError): |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
226 raise ValueError( |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
227 '!libervia_generate_pwd map arguments is missing. At least "name" ' |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
228 "must be specified." |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
229 ) |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
230 pwd_data_key = f"pwd_{name}" |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
231 try: |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
232 key = self._app_persistent_data[pwd_data_key] |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
233 except KeyError: |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
234 alphabet = string.ascii_letters + string.digits |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
235 key_size = int(kwargs.get("size", 30)) |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
236 key = "".join(secrets.choice(alphabet) for __ in range(key_size)) |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
237 self._app_persistent_data[pwd_data_key] = key |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
238 else: |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
239 log.debug(f"Re-using existing key for {name!r} password.") |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
240 return key |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
241 |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
242 def _libervia_param_constr(self, loader, node) -> str: |
3372 | 243 """Get a parameter specified when starting the application |
244 | |
245 The value can be either the name of the parameter to get, or a list as | |
246 [name, default_value] | |
247 """ | |
248 try: | |
249 name, default = loader.construct_sequence(node) | |
250 except ConstructorError: | |
251 name, default = loader.construct_scalar(node), None | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
252 assert self._params is not None |
3372 | 253 return self._params.get(name, default) |
254 | |
255 def register(self, manager): | |
256 name = manager.name | |
257 if name in self._managers: | |
258 raise exceptions.ConflictError( | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
259 f"There is already a manager with the name {name}" |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
260 ) |
3372 | 261 self._managers[manager.name] = manager |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
262 if manager.discover_path is not None: |
3372 | 263 self.discover(manager.discover_path, manager) |
264 | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
265 def get_manager(self, app_data: dict) -> AppManagerBackend: |
3372 | 266 """Get manager instance needed for this app |
267 | |
268 @raise exceptions.DataError: something is wrong with the type | |
269 @raise exceptions.NotFound: manager is not registered | |
270 """ | |
271 try: | |
272 app_type = app_data["type"] | |
273 except KeyError: | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
274 raise exceptions.DataError('app file doesn\'t have the mandatory "type" key') |
3372 | 275 if not isinstance(app_type, str): |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
276 raise exceptions.DataError(f"invalid app data type: {app_type!r}") |
3372 | 277 app_type = app_type.strip() |
278 try: | |
279 return self._managers[app_type] | |
280 except KeyError: | |
281 raise exceptions.NotFound( | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
282 f"No manager found to manage app of type {app_type!r}" |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
283 ) |
3372 | 284 |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
285 def get_app_data(self, id_type: Optional[str], identifier: str) -> dict: |
3372 | 286 """Retrieve instance's app_data from identifier |
287 | |
288 @param id_type: type of the identifier, can be: | |
289 - "name": identifier is a canonical application name | |
290 the first found instance of this application is returned | |
291 - "instance": identifier is an instance id | |
292 @param identifier: identifier according to id_type | |
293 @return: instance application data | |
294 @raise exceptions.NotFound: no instance with this id can be found | |
295 @raise ValueError: id_type is invalid | |
296 """ | |
297 if not id_type: | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
298 id_type = "name" |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
299 if id_type == "name": |
3372 | 300 identifier = identifier.lower().strip() |
301 try: | |
302 return next(iter(self._started[identifier])) | |
303 except (KeyError, StopIteration): | |
304 raise exceptions.NotFound( | |
305 f"No instance of {identifier!r} is currently running" | |
306 ) | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
307 elif id_type == "instance": |
3372 | 308 instance_id = identifier |
309 try: | |
310 return self._instances[instance_id] | |
311 except KeyError: | |
312 raise exceptions.NotFound( | |
313 f"There is no application instance running with id {instance_id!r}" | |
314 ) | |
315 else: | |
316 raise ValueError(f"invalid id_type: {id_type!r}") | |
317 | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
318 def discover(self, dir_path: Path, manager: AppManagerBackend | None = None) -> None: |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
319 """Search for app configuration file. |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
320 |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
321 App configuration files must start with [APP_FILE_PREFIX] and have a ``.yaml`` |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
322 extension. |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
323 """ |
3372 | 324 for file_path in dir_path.glob(f"{APP_FILE_PREFIX}*.yaml"): |
325 if manager is None: | |
326 try: | |
327 app_data = self.parse(file_path) | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
328 manager = self.get_manager(app_data) |
3372 | 329 except (exceptions.DataError, exceptions.NotFound) as e: |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
330 log.warning(f"Can't parse {file_path}, skipping: {e}") |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
331 continue |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
332 app_name = file_path.stem[len(APP_FILE_PREFIX) :].strip().lower() |
3372 | 333 if not app_name: |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
334 log.warning(f"invalid app file name at {file_path}") |
3372 | 335 continue |
336 app_dict = self._apps.setdefault(app_name, {}) | |
337 manager_set = app_dict.setdefault(manager, set()) | |
338 manager_set.add(file_path) | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
339 log.debug(f"{app_name!r} {manager.name} application found") |
3372 | 340 |
341 def parse(self, file_path: Path, params: Optional[dict] = None) -> dict: | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
342 """Parse Libervia application file |
3372 | 343 |
344 @param params: parameters for running this instance | |
345 @raise exceptions.DataError: something is wrong in the file | |
346 """ | |
347 if params is None: | |
348 params = {} | |
349 with file_path.open() as f: | |
350 # we set parameters to be used only with this instance | |
351 # no async method must used between this assignation and `load` | |
352 self._params = params | |
353 app_data = self.load(f) | |
354 self._params = None | |
355 if "name" not in app_data: | |
356 # note that we don't use lower() here as we want human readable name and | |
357 # uppercase may be set on purpose | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
358 app_data["name"] = file_path.stem[len(APP_FILE_PREFIX) :].strip() |
3372 | 359 single_instance = app_data.setdefault("single_instance", True) |
360 if not isinstance(single_instance, bool): | |
361 raise ValueError( | |
362 f'"single_instance" must be a boolean, but it is {type(single_instance)}' | |
363 ) | |
364 return app_data | |
365 | |
366 def list_applications(self, filters: Optional[List[str]]) -> List[str]: | |
367 """List available application | |
368 | |
369 @param filters: only show applications matching those filters. | |
370 using None will list all known applications | |
371 a filter can be: | |
372 - available: applications available locally | |
373 - running: only show launched applications | |
374 """ | |
375 if not filters: | |
376 return list(self.apps) | |
377 found = set() | |
378 for filter_ in filters: | |
379 if filter_ == "available": | |
380 found.update(self._apps) | |
381 elif filter_ == "running": | |
382 found.update(self._started) | |
383 else: | |
384 raise ValueError(f"Unknown filter: {filter_}") | |
385 return list(found) | |
386 | |
387 def _start(self, app_name, extra): | |
388 extra = data_format.deserialise(extra) | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
389 d = defer.ensureDeferred(self.start(str(app_name), extra)) |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
390 d.addCallback(data_format.serialise) |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
391 return d |
3372 | 392 |
393 async def start( | |
394 self, | |
395 app_name: str, | |
396 extra: Optional[dict] = None, | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
397 ) -> dict: |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
398 """Start an application |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
399 |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
400 @param app_name: name of the application to start |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
401 @param extra: extra parameters |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
402 @return: data with following keys: |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
403 - name (str): canonical application name |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
404 - instance (str): instance ID |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
405 - started (bool): True if the application is already started |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
406 if False, the "application_started" signal should be used to get notificed |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
407 when the application is actually started |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
408 - expose (dict): exposed data as given by [self.get_exposed] |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
409 exposed data which need to be computed are NOT returned, they will |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
410 available when the app will be fully started, throught the |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
411 [self.get_exposed] method. |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
412 """ |
3372 | 413 # FIXME: for now we use the first app manager available for the requested app_name |
414 # TODO: implement running multiple instance of the same app if some metadata | |
415 # to be defined in app_data allows explicitly it. | |
416 app_name = app_name.lower().strip() | |
417 try: | |
418 app_file_path = next(iter(next(iter(self._apps[app_name].values())))) | |
419 except KeyError: | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
420 raise exceptions.NotFound(f"No application found with the name {app_name!r}") |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
421 log.info(f"starting {app_name!r}") |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
422 self._app_persistent_data = await self.persistent_data.get(app_name) or {} |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
423 self._app_persistent_data["last_started"] = time.time() |
3372 | 424 started_data = self._started.setdefault(app_name, []) |
425 app_data = self.parse(app_file_path, extra) | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
426 await self.persistent_data.aset(app_name, self._app_persistent_data) |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
427 app_data["_started"] = False |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
428 app_data["_file_path"] = app_file_path |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
429 app_data["_name_canonical"] = app_name |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
430 single_instance = app_data["single_instance"] |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
431 ret_data = {"name": app_name, "started": False} |
3372 | 432 if single_instance: |
433 if started_data: | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
434 instance_data = started_data[0] |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
435 instance_id = instance_data["_instance_id"] |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
436 ret_data["instance"] = instance_id |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
437 ret_data["started"] = instance_data["_started"] |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
438 ret_data["expose"] = await self.get_exposed( |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
439 instance_id, "instance", {"skip_compute": True} |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
440 ) |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
441 log.info(f"{app_name!r} is already started or being started") |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
442 return ret_data |
3372 | 443 else: |
4037
524856bd7b19
massive refactoring to switch from camelCase to snake_case:
Goffi <goffi@goffi.org>
parents:
3998
diff
changeset
|
444 cache_path = self.host.memory.get_cache_path( |
3372 | 445 PLUGIN_INFO[C.PI_IMPORT_NAME], app_name |
446 ) | |
447 cache_path.mkdir(0o700, parents=True, exist_ok=True) | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
448 app_data["_instance_dir_path"] = cache_path |
3372 | 449 else: |
4085
c93b02000ae4
plugin app manager: rename `sat` to `libervia`
Goffi <goffi@goffi.org>
parents:
4071
diff
changeset
|
450 dest_dir_obj = tempfile.TemporaryDirectory(prefix="libervia_app_") |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
451 app_data["_instance_dir_obj"] = dest_dir_obj |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
452 app_data["_instance_dir_path"] = Path(dest_dir_obj.name) |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
453 instance_id = ret_data["instance"] = app_data["_instance_id"] = shortuuid.uuid() |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
454 manager = self.get_manager(app_data) |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
455 app_data["_manager"] = manager |
3372 | 456 started_data.append(app_data) |
457 self._instances[instance_id] = app_data | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
458 # we retrieve exposed data such as url_prefix which can be useful computed exposed |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
459 # data must wait for the app to be started, so we skip them for now |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
460 ret_data["expose"] = await self.get_exposed( |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
461 instance_id, "instance", {"skip_compute": True} |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
462 ) |
3372 | 463 |
464 try: | |
465 start = manager.start | |
466 except AttributeError: | |
467 raise exceptions.InternalError( | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
468 f'{manager.name} doesn\'t have the mandatory "start" method' |
3372 | 469 ) |
470 else: | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
471 defer.ensureDeferred(self.start_app(start, app_data)) |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
472 return ret_data |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
473 |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
474 async def start_app(self, start_cb: Callable, app_data: dict) -> None: |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
475 app_name = app_data["_name_canonical"] |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
476 instance_id = app_data["_instance_id"] |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
477 try: |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
478 await start_cb(app_data) |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
479 except Exception as e: |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
480 log.exception(f"Can't start libervia app {app_name!r}") |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
481 self.host.bridge.application_error( |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
482 app_name, |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
483 instance_id, |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
484 data_format.serialise({"class": str(type(e)), "msg": str(e)}), |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
485 ) |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
486 else: |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
487 app_data["_started"] = True |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
488 self.host.bridge.application_started(app_name, instance_id, "") |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
489 log.info(f"{app_name!r} started") |
3372 | 490 |
491 def _stop(self, identifier, id_type, extra): | |
492 extra = data_format.deserialise(extra) | |
493 return defer.ensureDeferred( | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
494 self.stop(str(identifier), str(id_type) or None, extra) |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
495 ) |
3372 | 496 |
497 async def stop( | |
498 self, | |
499 identifier: str, | |
500 id_type: Optional[str] = None, | |
501 extra: Optional[dict] = None, | |
502 ) -> None: | |
503 if extra is None: | |
504 extra = {} | |
505 | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
506 app_data = self.get_app_data(id_type, identifier) |
3372 | 507 |
508 log.info(f"stopping {app_data['name']!r}") | |
509 | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
510 app_name = app_data["_name_canonical"] |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
511 instance_id = app_data["_instance_id"] |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
512 manager = app_data["_manager"] |
3372 | 513 |
514 try: | |
515 stop = manager.stop | |
516 except AttributeError: | |
517 raise exceptions.InternalError( | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
518 f'{manager.name} doesn\'t have the mandatory "stop" method' |
3372 | 519 ) |
520 else: | |
521 try: | |
522 await stop(app_data) | |
523 except Exception as e: | |
524 log.warning( | |
525 f"Instance {instance_id} of application {app_name} can't be stopped " | |
526 f"properly: {e}" | |
527 ) | |
528 return | |
529 | |
530 try: | |
531 del self._instances[instance_id] | |
532 except KeyError: | |
533 log.error( | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
534 f"INTERNAL ERROR: {instance_id!r} is not present in self._instances" |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
535 ) |
3372 | 536 |
537 try: | |
538 self._started[app_name].remove(app_data) | |
539 except ValueError: | |
540 log.error( | |
541 "INTERNAL ERROR: there is no app data in self._started with id " | |
542 f"{instance_id!r}" | |
543 ) | |
544 | |
545 log.info(f"{app_name!r} stopped") | |
546 | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
547 def _get_exposed(self, identifier, id_type, extra): |
3372 | 548 extra = data_format.deserialise(extra) |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
549 d = defer.ensureDeferred(self.get_exposed(identifier, id_type, extra)) |
3372 | 550 d.addCallback(lambda d: data_format.serialise(d)) |
551 return d | |
552 | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
553 def get_app_data_value(self, path: list[str], data: dict[str, Any]) -> Any: |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
554 """Return a value from app data from its path. |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
555 |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
556 @param path: sequence of keys to get to retrieve the value |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
557 @return: requested value |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
558 @raise KeyError: Can't find the requested value. |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
559 """ |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
560 return reduce(lambda l, k: l[k], path, data) |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
561 |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
562 async def get_exposed( |
3372 | 563 self, |
564 identifier: str, | |
565 id_type: Optional[str] = None, | |
566 extra: Optional[dict] = None, | |
567 ) -> dict: | |
568 """Get data exposed by the application | |
569 | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
570 The manager's "compute_expose" method will be called if it exists. It can be used |
3372 | 571 to handle manager specific conventions. |
572 """ | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
573 app_data = self.get_app_data(id_type, identifier) |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
574 if app_data.get("_exposed_computed", False): |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
575 return app_data["expose"] |
3372 | 576 if extra is None: |
577 extra = {} | |
578 expose = app_data.setdefault("expose", {}) | |
579 if "passwords" in expose: | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
580 passwords = expose["passwords"] |
3372 | 581 for name, value in list(passwords.items()): |
582 if isinstance(value, list): | |
583 # if we have a list, is the sequence of keys leading to the value | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
584 # to expose. |
3372 | 585 try: |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
586 passwords[name] = self.get_app_data_value(value, app_data) |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
587 except KeyError: |
3372 | 588 log.warning( |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
589 f"Can't retrieve exposed value for password {name!r}: {e}" |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
590 ) |
3372 | 591 del passwords[name] |
592 | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
593 for key in ("url_prefix", "front_url"): |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
594 value = expose.get(key) |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
595 if isinstance(value, list): |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
596 try: |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
597 expose[key] = self.get_app_data_value(value, app_data) |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
598 except KeyError: |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
599 log.warning(f"Can't retrieve exposed value for {key!r} at {value}") |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
600 del expose[key] |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
601 |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
602 front_url = expose.get("front_url") |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
603 if front_url: |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
604 # we want to be sure that a scheme is defined, defaulting to ``https`` |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
605 parsed_url = urlparse(front_url) |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
606 if not parsed_url.scheme: |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
607 if not parsed_url.netloc: |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
608 path_elt = parsed_url.path.split("/", 1) |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
609 parsed_url = parsed_url._replace( |
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
610 netloc=path_elt[0], |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
611 path=f"/{path_elt[1]}" if len(path_elt) > 1 else "", |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
612 ) |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
613 parsed_url = parsed_url._replace(scheme="https") |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
614 expose["front_url"] = urlunparse(parsed_url) |
3372 | 615 |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
616 if extra.get("skip_compute", False): |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
617 return expose |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
618 |
3372 | 619 try: |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
620 compute_expose = app_data["_manager"].compute_expose |
3372 | 621 except AttributeError: |
622 pass | |
623 else: | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
624 await compute_expose(app_data) |
3372 | 625 |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
626 app_data["_exposed_computed"] = True |
3372 | 627 return expose |
628 | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
629 async def _do_prepare( |
3372 | 630 self, |
631 app_data: dict, | |
632 ) -> None: | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
633 name = app_data["name"] |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
634 dest_path = app_data["_instance_dir_path"] |
3372 | 635 if next(dest_path.iterdir(), None) != None: |
636 log.debug(f"There is already a prepared dir at {dest_path}, nothing to do") | |
637 return | |
638 try: | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
639 prepare = app_data["prepare"].copy() |
3372 | 640 except KeyError: |
641 prepare = {} | |
642 | |
643 if not prepare: | |
4247
4aa62767f501
plugin app manager: various improvements:
Goffi <goffi@goffi.org>
parents:
4085
diff
changeset
|
644 log.debug(f"Nothing to prepare for {name!r}") |
3372 | 645 return |
646 | |
647 for action, value in list(prepare.items()): | |
648 log.debug(f"[{name}] [prepare] running {action!r} action") | |
649 if action == "git": | |
650 try: | |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
651 git_path = which("git")[0] |
3372 | 652 except IndexError: |
653 raise exceptions.NotFound( | |
654 "Can't find \"git\" executable, {name} can't be started without it" | |
655 ) | |
656 await async_process.run(git_path, "clone", value, str(dest_path)) | |
657 log.debug(f"{value!r} git repository cloned at {dest_path}") | |
658 else: | |
659 raise NotImplementedError( | |
660 f"{action!r} is not managed, can't start {name}" | |
661 ) | |
662 del prepare[action] | |
663 | |
664 if prepare: | |
665 raise exceptions.InternalError('"prepare" should be empty') | |
666 | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
667 async def _do_create_files( |
3565
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
668 self, |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
669 app_data: dict, |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
670 ) -> None: |
4270
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
671 dest_path = app_data["_instance_dir_path"] |
0d7bb4df2343
Reformatted code base using black.
Goffi <goffi@goffi.org>
parents:
4247
diff
changeset
|
672 files = app_data.get("files") |
3565
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
673 if not files: |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
674 return |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
675 if not isinstance(files, dict): |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
676 raise ValueError('"files" must be a dictionary') |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
677 for filename, data in files.items(): |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
678 path = dest_path / filename |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
679 if path.is_file(): |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
680 log.info(f"{path} already exists, skipping") |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
681 with path.open("w") as f: |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
682 f.write(data.get("content", "")) |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
683 log.debug(f"{path} created") |
d66a8453b02b
plugin app manager: add a way to create files:
Goffi <goffi@goffi.org>
parents:
3564
diff
changeset
|
684 |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
685 async def start_common(self, app_data: dict) -> None: |
3372 | 686 """Method running common action when starting a manager |
687 | |
688 It should be called by managers in "start" method. | |
689 """ | |
3998
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
690 await self._do_prepare(app_data) |
402d31527af4
plugin app manager: `start` doesn't wait anymore for actual app start:
Goffi <goffi@goffi.org>
parents:
3565
diff
changeset
|
691 await self._do_create_files(app_data) |