Mercurial > libervia-backend
annotate libervia/backend/plugins/plugin_misc_app_manager/__init__.py @ 4332:71c939e34ca6
XEP-0373 (OX): Adjust to gpgme updates: generate with explicit algorithm and subkeys
author | Syndace <me@syndace.dev> |
---|---|
date | Sat, 13 Jul 2024 18:28:28 +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) |