lockss-turtles 0.6.0.dev24__py3-none-any.whl → 0.6.0.dev25__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lockss/turtles/__init__.py +1 -1
- lockss/turtles/app.py +346 -53
- lockss/turtles/cli.py +370 -99
- lockss/turtles/plugin.py +2 -3
- lockss/turtles/plugin_registry.py +5 -6
- lockss/turtles/plugin_set.py +24 -24
- lockss/turtles/plugin_signing_credentials.py +80 -0
- lockss/turtles/util.py +2 -3
- {lockss_turtles-0.6.0.dev24.dist-info → lockss_turtles-0.6.0.dev25.dist-info}/METADATA +4 -3
- lockss_turtles-0.6.0.dev25.dist-info/RECORD +17 -0
- {lockss_turtles-0.6.0.dev24.dist-info → lockss_turtles-0.6.0.dev25.dist-info}/WHEEL +1 -1
- unittest/lockss/turtles/__init__.py +53 -12
- unittest/lockss/turtles/test_plugin_registry.py +411 -0
- unittest/lockss/turtles/test_plugin_set.py +233 -38
- lockss_turtles-0.6.0.dev24.dist-info/RECORD +0 -15
- {lockss_turtles-0.6.0.dev24.dist-info → lockss_turtles-0.6.0.dev25.dist-info}/entry_points.txt +0 -0
- {lockss_turtles-0.6.0.dev24.dist-info → lockss_turtles-0.6.0.dev25.dist-info/licenses}/LICENSE +0 -0
lockss/turtles/__init__.py
CHANGED
lockss/turtles/app.py
CHANGED
|
@@ -28,80 +28,156 @@
|
|
|
28
28
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
29
29
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
"""
|
|
32
|
+
Module to represent Turtles operations.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
# Remove in Python 3.14; see https://stackoverflow.com/a/33533514
|
|
33
36
|
from __future__ import annotations
|
|
34
37
|
|
|
38
|
+
# Remove in Python 3.11; see https://docs.python.org/3.11/library/exceptions.html#exception-groups
|
|
39
|
+
from exceptiongroup import ExceptionGroup
|
|
40
|
+
|
|
35
41
|
from collections.abc import Callable, Iterable
|
|
36
42
|
from pathlib import Path
|
|
37
|
-
from typing import ClassVar,
|
|
43
|
+
from typing import ClassVar, Optional, Union
|
|
38
44
|
|
|
39
|
-
from exceptiongroup import ExceptionGroup
|
|
40
45
|
from lockss.pybasic.fileutil import path
|
|
41
|
-
from pydantic import
|
|
46
|
+
from pydantic import ValidationError
|
|
42
47
|
import xdg
|
|
43
48
|
import yaml
|
|
44
49
|
|
|
45
50
|
from .plugin import Plugin, PluginIdentifier
|
|
46
|
-
from .plugin_registry import PluginRegistry, PluginRegistryCatalog, PluginRegistryCatalogKind, PluginRegistryKind, PluginRegistryLayerIdentifier
|
|
51
|
+
from .plugin_registry import PluginRegistry, PluginRegistryCatalog, PluginRegistryCatalogKind, PluginRegistryIdentifier, PluginRegistryKind, PluginRegistryLayerIdentifier
|
|
47
52
|
from .plugin_set import PluginSet, PluginSetCatalog, PluginSetCatalogKind, PluginSetKind
|
|
48
|
-
from .
|
|
53
|
+
from .plugin_signing_credentials import PluginSigningCredentials, PluginSigningCredentialsKind
|
|
54
|
+
from .util import PathOrStr
|
|
49
55
|
|
|
50
56
|
|
|
57
|
+
#: Type alias for the result of a single plugin building operation.
|
|
58
|
+
#: First item (index 0): identifier of the plugin set that had the given plugin.
|
|
59
|
+
#: Second item (index 1): plugin JAR file path (or None if not built).
|
|
60
|
+
#: Third item (index 2): plugin object (or None if not built).
|
|
61
|
+
BuildPluginResult = tuple[str, Optional[Path], Optional[Plugin]]
|
|
51
62
|
|
|
52
|
-
PluginSigningCredentialsKind = Literal['PluginSigningCredentials']
|
|
53
63
|
|
|
64
|
+
#: Type alias for the result of a single plugin deployment operation to a given
|
|
65
|
+
#: plugin registry layer.
|
|
66
|
+
#: First item (index 0):
|
|
67
|
+
#: Second item (index 1):
|
|
68
|
+
#: Third item (index 2): deployed JAR file path (or None if not deployed).
|
|
69
|
+
#: Fourth item (index 3): plugin object (or None if not deployed).
|
|
70
|
+
DeployPluginResult = tuple[PluginRegistryIdentifier, PluginRegistryLayerIdentifier, Optional[Path], Optional[Plugin]]
|
|
54
71
|
|
|
55
|
-
class PluginSigningCredentials(BaseModelWithRoot):
|
|
56
|
-
kind: PluginSigningCredentialsKind = Field(description="This object's kind")
|
|
57
|
-
plugin_signing_keystore: str = Field(title='Plugin Signing Keystore', description='A path to the plugin signing keystore', alias='plugin-signing-keystore')
|
|
58
|
-
plugin_signing_alias: str = Field(title='Plugin Signing Alias', description='The plugin signing alias to use', alias='plugin-signing-alias')
|
|
59
72
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return self.get_root().joinpath(self.plugin_signing_keystore)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
class TurtlesApp(object):
|
|
73
|
+
class Turtles(object):
|
|
74
|
+
"""
|
|
75
|
+
A Turtles command object, which can be used to execute Turtles operations.
|
|
76
|
+
"""
|
|
68
77
|
|
|
78
|
+
#: The name of a Turtles configuration directory.
|
|
69
79
|
CONFIG_DIR_NAME: ClassVar[str] = 'lockss-turtles'
|
|
70
80
|
|
|
81
|
+
#: The Turtles configuration directory under ``$XDG_CONFIG_HOME`` (by
|
|
82
|
+
# default ``$HOME/.config``, which is typically ``/home/$USER/.config``).
|
|
71
83
|
XDG_CONFIG_DIR: ClassVar[Path] = Path(xdg.xdg_config_home(), CONFIG_DIR_NAME)
|
|
72
84
|
|
|
73
|
-
|
|
74
|
-
|
|
85
|
+
#: The Turtles configuration directory under ``/etc``.
|
|
75
86
|
ETC_CONFIG_DIR: ClassVar[Path] = Path('/etc', CONFIG_DIR_NAME)
|
|
76
87
|
|
|
88
|
+
#: The Turtles configuration directory under ``/usr/local/share``.
|
|
89
|
+
USR_CONFIG_DIR: ClassVar[Path] = Path('/usr/local/share', CONFIG_DIR_NAME)
|
|
90
|
+
|
|
91
|
+
#: The Turtles configuration directories in order of preference:
|
|
92
|
+
#: ``XDG_CONFIG_DIR``, ``ETC_CONFIG_DIR``, ``USR_CONFIG_DIR``
|
|
77
93
|
CONFIG_DIRS: ClassVar[tuple[Path, ...]] = (XDG_CONFIG_DIR, ETC_CONFIG_DIR, USR_CONFIG_DIR)
|
|
78
94
|
|
|
95
|
+
#: The default plugin registry catalog file name.
|
|
79
96
|
PLUGIN_REGISTRY_CATALOG: ClassVar[str] = 'plugin-registry-catalog.yaml'
|
|
80
97
|
|
|
98
|
+
#: The default plugin set catalog file name.
|
|
81
99
|
PLUGIN_SET_CATALOG: ClassVar[str] = 'plugin-set-catalog.yaml'
|
|
82
100
|
|
|
101
|
+
#: The default plugin signing credentials file name.
|
|
83
102
|
PLUGIN_SIGNING_CREDENTIALS: ClassVar[str] = 'plugin-signing-credentials.yaml'
|
|
84
103
|
|
|
85
104
|
def __init__(self) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Constructor.
|
|
107
|
+
"""
|
|
86
108
|
super().__init__()
|
|
87
|
-
self.
|
|
109
|
+
self._plugin_signing_password_callable: Optional[Callable[[], str]] = None
|
|
88
110
|
self._plugin_registries: list[PluginRegistry] = list()
|
|
89
111
|
self._plugin_registry_catalogs: list[PluginRegistryCatalog] = list()
|
|
90
112
|
self._plugin_set_catalogs: list[PluginSetCatalog] = list()
|
|
91
113
|
self._plugin_sets: list[PluginSet] = list()
|
|
92
114
|
self._plugin_signing_credentials: Optional[PluginSigningCredentials] = None
|
|
93
115
|
|
|
94
|
-
def build_plugin(self,
|
|
116
|
+
def build_plugin(self,
|
|
117
|
+
plugin_id_or_plugin_ids: Union[PluginIdentifier, list[PluginIdentifier]]) -> dict[str, BuildPluginResult]:
|
|
118
|
+
"""
|
|
119
|
+
Builds zero or more plugins.
|
|
120
|
+
|
|
121
|
+
:param plugin_id_or_plugin_ids: Either one plugin identifier, or a list
|
|
122
|
+
of plugin identifiers.
|
|
123
|
+
:type plugin_id_or_plugin_ids: Union[PluginIdentifier, list[PluginIdentifier]]
|
|
124
|
+
:return: A mapping from plugin identifier to build plugin result; if no
|
|
125
|
+
plugin identifiers were given, the result is an empty mapping.
|
|
126
|
+
:rtype: dict[str, BuildPluginResult]
|
|
127
|
+
:raises Exception: If a given plugin identifier is not found in any
|
|
128
|
+
loaded plugin set.
|
|
129
|
+
"""
|
|
130
|
+
plugin_ids: list[PluginIdentifier] = plugin_id_or_plugin_ids if isinstance(plugin_id_or_plugin_ids, list) else [plugin_id_or_plugin_ids]
|
|
95
131
|
return {plugin_id: self._build_one_plugin(plugin_id) for plugin_id in plugin_ids}
|
|
96
132
|
|
|
97
|
-
def deploy_plugin(self,
|
|
98
|
-
|
|
133
|
+
def deploy_plugin(self,
|
|
134
|
+
src_path_or_src_paths: Union[Path, list[Path]],
|
|
135
|
+
layer_id_or_layer_ids: Union[PluginRegistryLayerIdentifier, list[PluginRegistryLayerIdentifier]],
|
|
136
|
+
interactive: bool=False) -> dict[tuple[Path, PluginIdentifier], list[DeployPluginResult]]:
|
|
137
|
+
"""
|
|
138
|
+
Deploys zero or more plugins.
|
|
139
|
+
|
|
140
|
+
:param src_path_or_src_paths: Either one signed JAR file paths or a list
|
|
141
|
+
of signed JAR file paths.
|
|
142
|
+
:type src_path_or_src_paths: Union[Path, list[Path]]
|
|
143
|
+
:param layer_id_or_layer_ids: Either one plugin registry layer
|
|
144
|
+
identifier or a list of plugin registry
|
|
145
|
+
layer identifiers.
|
|
146
|
+
:type layer_id_or_layer_ids: Union[PluginRegistryLayerIdentifier, list[PluginRegistryLayerIdentifier]]
|
|
147
|
+
:param interactive: Whether interactive prompts are allowed (default
|
|
148
|
+
False).
|
|
149
|
+
:type interactive: bool
|
|
150
|
+
:return: A mapping from tuples of signed JAR file path and corresponding
|
|
151
|
+
plugin identifier to a list of build deployment results (one
|
|
152
|
+
per plugin registry layer); if no signed JAR file paths were
|
|
153
|
+
given, the result is an empty mapping.
|
|
154
|
+
:rtype: dict[tuple[Path, PluginIdentifier], list[DeployPluginResult]]
|
|
155
|
+
:raises Exception: If a given plugin is not declared in any loaded
|
|
156
|
+
plugin registry.
|
|
157
|
+
"""
|
|
158
|
+
src_paths: list[Path] = src_path_or_src_paths if isinstance(src_path_or_src_paths, list) else [src_path_or_src_paths]
|
|
159
|
+
layer_ids: list[PluginRegistryLayerIdentifier] = layer_id_or_layer_ids if isinstance(layer_id_or_layer_ids, list) else [layer_id_or_layer_ids]
|
|
160
|
+
plugin_ids = [Plugin.id_from_jar(src_path) for src_path in src_paths] # FIXME: should go down to _deploy_one_plugin?
|
|
99
161
|
return {(src_path, plugin_id): self._deploy_one_plugin(src_path,
|
|
100
162
|
plugin_id,
|
|
101
163
|
layer_ids,
|
|
102
164
|
interactive=interactive) for src_path, plugin_id in zip(src_paths, plugin_ids)}
|
|
103
165
|
|
|
104
|
-
def load_plugin_registries(self,
|
|
166
|
+
def load_plugin_registries(self,
|
|
167
|
+
plugin_registry_path_or_str: PathOrStr) -> Turtles:
|
|
168
|
+
"""
|
|
169
|
+
Processes the given YAML file, loading all plugin registry definitions
|
|
170
|
+
it contains, ignoring other YAML objects.
|
|
171
|
+
|
|
172
|
+
:param plugin_registry_path_or_str: A file path (or string).
|
|
173
|
+
:type plugin_registry_path_or_str: PathOrStr
|
|
174
|
+
:return: This Turtles object (for chaining).
|
|
175
|
+
:rtype: Turtles
|
|
176
|
+
:raises ExceptionGroup: If one or more errors occur while loading plugin
|
|
177
|
+
registry definitions.
|
|
178
|
+
:raises ValueError: If the given file has already been processed or if
|
|
179
|
+
it contains no plugin registry definitions.
|
|
180
|
+
"""
|
|
105
181
|
plugin_registry_path = path(plugin_registry_path_or_str)
|
|
106
182
|
if plugin_registry_path in map(lambda pr: pr.get_root(), self._plugin_registries):
|
|
107
183
|
raise ValueError(f'Plugin registries already loaded from: {plugin_registry_path!s}')
|
|
@@ -121,7 +197,23 @@ class TurtlesApp(object):
|
|
|
121
197
|
raise ValueError(f'No plugin registries found in: {plugin_registry_path!s}')
|
|
122
198
|
return self
|
|
123
199
|
|
|
124
|
-
def load_plugin_registry_catalogs(self,
|
|
200
|
+
def load_plugin_registry_catalogs(self,
|
|
201
|
+
plugin_registry_catalog_path_or_str: PathOrStr) -> Turtles:
|
|
202
|
+
"""
|
|
203
|
+
Processes the given YAML file, loading all plugin registry catalog
|
|
204
|
+
definitions it contains and in turn all plugin registry definitions they
|
|
205
|
+
reference, ignoring other YAML objects.
|
|
206
|
+
|
|
207
|
+
:param plugin_registry_catalog_path_or_str: A file path (or string).
|
|
208
|
+
:type plugin_registry_catalog_path_or_str: PathOrStr
|
|
209
|
+
:return: This Turtles object (for chaining).
|
|
210
|
+
:rtype: Turtles
|
|
211
|
+
:raises ExceptionGroup: If one or more errors occur while loading plugin
|
|
212
|
+
registry catalog definitions or the plugin
|
|
213
|
+
registry definitions they reference.
|
|
214
|
+
:raises ValueError: If the given file has already been processed or if
|
|
215
|
+
it contains no plugin registry catalog definitions.
|
|
216
|
+
"""
|
|
125
217
|
plugin_registry_catalog_path = path(plugin_registry_catalog_path_or_str)
|
|
126
218
|
if plugin_registry_catalog_path in map(lambda prc: prc.get_root(), self._plugin_registry_catalogs):
|
|
127
219
|
raise ValueError(f'Plugin registry catalogs already loaded from: {plugin_registry_catalog_path!s}')
|
|
@@ -148,7 +240,23 @@ class TurtlesApp(object):
|
|
|
148
240
|
raise ValueError(f'No plugin registry catalogs found in: {plugin_registry_catalog_path!s}')
|
|
149
241
|
return self
|
|
150
242
|
|
|
151
|
-
def load_plugin_set_catalogs(self,
|
|
243
|
+
def load_plugin_set_catalogs(self,
|
|
244
|
+
plugin_set_catalog_path_or_str: PathOrStr) -> Turtles:
|
|
245
|
+
"""
|
|
246
|
+
Processes the given YAML file, loading all plugin set catalog
|
|
247
|
+
definitions it contains and in turn all plugin set definitions they
|
|
248
|
+
reference, ignoring other YAML objects.
|
|
249
|
+
|
|
250
|
+
:param plugin_set_catalog_path_or_str: A file path (or string).
|
|
251
|
+
:type plugin_set_catalog_path_or_str: PathOrStr
|
|
252
|
+
:return: This Turtles object (for chaining).
|
|
253
|
+
:rtype: Turtles
|
|
254
|
+
:raises ExceptionGroup: If one or more errors occur while loading plugin
|
|
255
|
+
set catalog definitions or the plugin set
|
|
256
|
+
definitions they reference.
|
|
257
|
+
:raises ValueError: If the given file has already been processed or if
|
|
258
|
+
it contains no plugin set catalog definitions.
|
|
259
|
+
"""
|
|
152
260
|
plugin_set_catalog_path = path(plugin_set_catalog_path_or_str)
|
|
153
261
|
if plugin_set_catalog_path in map(lambda psc: psc.get_root(), self._plugin_set_catalogs):
|
|
154
262
|
raise ValueError(f'Plugin set catalogs already loaded from: {plugin_set_catalog_path!s}')
|
|
@@ -175,7 +283,21 @@ class TurtlesApp(object):
|
|
|
175
283
|
raise ValueError(f'No plugin set catalogs found in: {plugin_set_catalog_path!s}')
|
|
176
284
|
return self
|
|
177
285
|
|
|
178
|
-
def load_plugin_sets(self,
|
|
286
|
+
def load_plugin_sets(self,
|
|
287
|
+
plugin_set_path_or_str: PathOrStr) -> Turtles:
|
|
288
|
+
"""
|
|
289
|
+
Processes the given YAML file, loading all plugin set definitions it
|
|
290
|
+
contains, ignoring other YAML objects.
|
|
291
|
+
|
|
292
|
+
:param plugin_set_path_or_str: A file path (or string).
|
|
293
|
+
:type plugin_set_path_or_str: PathOrStr
|
|
294
|
+
:return: This Turtles object (for chaining).
|
|
295
|
+
:rtype: Turtles
|
|
296
|
+
:raises ExceptionGroup: If one or more errors occur while loading plugin
|
|
297
|
+
set definitions.
|
|
298
|
+
:raises ValueError: If the given file has already been processed or if
|
|
299
|
+
it contains no plugin set definitions.
|
|
300
|
+
"""
|
|
179
301
|
plugin_set_path = path(plugin_set_path_or_str)
|
|
180
302
|
if plugin_set_path in map(lambda ps: ps.get_root(), self._plugin_sets):
|
|
181
303
|
raise ValueError(f'Plugin sets already loaded from: {plugin_set_path!s}')
|
|
@@ -195,7 +317,23 @@ class TurtlesApp(object):
|
|
|
195
317
|
raise ValueError(f'No plugin sets found in: {plugin_set_path!s}')
|
|
196
318
|
return self
|
|
197
319
|
|
|
198
|
-
def load_plugin_signing_credentials(self,
|
|
320
|
+
def load_plugin_signing_credentials(self,
|
|
321
|
+
plugin_signing_credentials_path_or_str: PathOrStr) -> Turtles:
|
|
322
|
+
"""
|
|
323
|
+
Processes the given YAML file, loading all plugin set definitions it
|
|
324
|
+
contains in search of exactly one, ignoring YAML objects of other kinds.
|
|
325
|
+
|
|
326
|
+
:param plugin_signing_credentials_path_or_str: A file path (or string).
|
|
327
|
+
:type plugin_signing_credentials_path_or_str: PathOrStr
|
|
328
|
+
:return: This Turtles object (for chaining).
|
|
329
|
+
:rtype: Turtles
|
|
330
|
+
:raises ExceptionGroup: If one or more errors occur while loading plugin
|
|
331
|
+
signing credentials definitions.
|
|
332
|
+
:raises ValueError: If the given file has already been processed, if it
|
|
333
|
+
contains no plugin signing credentials definitions,
|
|
334
|
+
or if it contains more than one plugin signing
|
|
335
|
+
credentials definitions.
|
|
336
|
+
"""
|
|
199
337
|
plugin_signing_credentials_path = path(plugin_signing_credentials_path_or_str)
|
|
200
338
|
if self._plugin_signing_credentials:
|
|
201
339
|
raise ValueError(f'Plugin signing credentials already loaded from: {self._plugin_signing_credentials.get_root()!s}')
|
|
@@ -216,7 +354,31 @@ class TurtlesApp(object):
|
|
|
216
354
|
raise ValueError(f'Multiple plugin signing credentials found in: {plugin_signing_credentials_path!s}')
|
|
217
355
|
return self
|
|
218
356
|
|
|
219
|
-
def release_plugin(self,
|
|
357
|
+
def release_plugin(self,
|
|
358
|
+
plugin_id_or_plugin_ids: Union[PluginIdentifier, list[PluginIdentifier]],
|
|
359
|
+
layer_id_or_layer_ids: Union[PluginRegistryLayerIdentifier, list[PluginRegistryLayerIdentifier]],
|
|
360
|
+
interactive: bool=False) -> dict[PluginIdentifier, list[DeployPluginResult]]:
|
|
361
|
+
"""
|
|
362
|
+
Releases (builds then deploys) zero or more plugins.
|
|
363
|
+
|
|
364
|
+
:param plugin_id_or_plugin_ids: Either one plugin identifier, or a list
|
|
365
|
+
of plugin identifiers.
|
|
366
|
+
:type plugin_id_or_plugin_ids: Union[PluginIdentifier, list[PluginIdentifier]]
|
|
367
|
+
:param layer_id_or_layer_ids: Either one plugin registry layer
|
|
368
|
+
identifier or a list of plugin registry
|
|
369
|
+
layer identifiers.
|
|
370
|
+
:type layer_id_or_layer_ids: Union[PluginRegistryLayerIdentifier, list[PluginRegistryLayerIdentifier]]
|
|
371
|
+
:param interactive: Whether interactive prompts are allowed (default
|
|
372
|
+
False).
|
|
373
|
+
:type interactive: bool
|
|
374
|
+
:return: A mapping from plugin identifier to plugin deployment result;
|
|
375
|
+
if no plugins were given, the result is an empty mapping.
|
|
376
|
+
:rtype: dict[PluginIdentifier, list[DeployPluginResult]]
|
|
377
|
+
:raises Exception: If a given plugin is not found in any plugin set or
|
|
378
|
+
is not declared in any loaded plugin registry.
|
|
379
|
+
"""
|
|
380
|
+
plugin_ids: list[PluginIdentifier] = plugin_id_or_plugin_ids if isinstance(plugin_id_or_plugin_ids, list) else [plugin_id_or_plugin_ids]
|
|
381
|
+
layer_ids: list[PluginRegistryLayerIdentifier] = layer_id_or_layer_ids if isinstance(layer_id_or_layer_ids, list) else [layer_id_or_layer_ids]
|
|
220
382
|
# ... plugin_id -> (set_id, jar_path, plugin)
|
|
221
383
|
ret1 = self.build_plugin(plugin_ids)
|
|
222
384
|
jar_paths = [jar_path for set_id, jar_path, plugin in ret1.values()]
|
|
@@ -226,10 +388,30 @@ class TurtlesApp(object):
|
|
|
226
388
|
interactive=interactive)
|
|
227
389
|
return {plugin_id: val for (jar_path, plugin_id), val in ret2.items()}
|
|
228
390
|
|
|
229
|
-
def
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
391
|
+
def set_plugin_signing_password(self,
|
|
392
|
+
callable_or_password: Union[str, Callable[[], str]]) -> None:
|
|
393
|
+
"""
|
|
394
|
+
Sets the plugin signing password callable.
|
|
395
|
+
|
|
396
|
+
:param callable_or_password: A callable returning a string (or simply a
|
|
397
|
+
string).
|
|
398
|
+
:type callable_or_password: Union[str, Callable[[], str]]
|
|
399
|
+
"""
|
|
400
|
+
self._plugin_signing_password_callable = callable_or_password if callable(callable_or_password) else lambda: callable_or_password
|
|
401
|
+
|
|
402
|
+
def _build_one_plugin(self,
|
|
403
|
+
plugin_id: PluginIdentifier) -> BuildPluginResult:
|
|
404
|
+
"""
|
|
405
|
+
Builds one plugin.
|
|
406
|
+
|
|
407
|
+
:param plugin_id: A plugin identifier.
|
|
408
|
+
:type plugin_id: PluginIdentifier
|
|
409
|
+
:return: A plugin build result object; if the plugin set returned None,
|
|
410
|
+
the second and third items (index 1 and 2) are None.
|
|
411
|
+
:rtype: BuildPluginResult
|
|
412
|
+
:raises Exception: If the given plugin identifier is not found in any
|
|
413
|
+
loaded plugin set.
|
|
414
|
+
"""
|
|
233
415
|
for plugin_set in self._plugin_sets:
|
|
234
416
|
if plugin_set.has_plugin(plugin_id):
|
|
235
417
|
bp = plugin_set.build_plugin(plugin_id,
|
|
@@ -237,15 +419,38 @@ class TurtlesApp(object):
|
|
|
237
419
|
self._get_plugin_signing_alias(),
|
|
238
420
|
self._get_plugin_signing_password())
|
|
239
421
|
return plugin_set.get_id(), bp[0] if bp else None, bp[1] if bp else None
|
|
240
|
-
raise Exception(f'
|
|
241
|
-
|
|
242
|
-
def _deploy_one_plugin(self,
|
|
422
|
+
raise Exception(f'plugin identifier not found in any loaded plugin set: {plugin_id}')
|
|
423
|
+
|
|
424
|
+
def _deploy_one_plugin(self,
|
|
425
|
+
src_jar: Path,
|
|
426
|
+
plugin_id: PluginIdentifier,
|
|
427
|
+
layer_ids: list[PluginRegistryLayerIdentifier],
|
|
428
|
+
interactive: bool=False) -> list[DeployPluginResult]:
|
|
429
|
+
"""
|
|
430
|
+
Deploys a single plugin to the provided plugin registry layers of all
|
|
431
|
+
loaded plugin registries that declare the given plugin.
|
|
432
|
+
|
|
433
|
+
:param src_jar: File path of the signed JAR.
|
|
434
|
+
:type src_jar: Path
|
|
435
|
+
:param plugin_id: The corresponding plugin identifier.
|
|
436
|
+
:type plugin_id: PluginIdentifier
|
|
437
|
+
:param layer_ids: A list of plugin layer identifiers.
|
|
438
|
+
:type layer_ids: list[PluginRegistryLayerIdentifier]
|
|
439
|
+
:param interactive: Whether interactive prompts are allowed (default
|
|
440
|
+
False).
|
|
441
|
+
:type interactive: bool
|
|
442
|
+
:return: A non-empty list of plugin deployment results; if for any, the
|
|
443
|
+
plugin registry returned None, the third and fourth items
|
|
444
|
+
(index 2 and 3) are None.
|
|
445
|
+
:rtype: list[DeployPluginResult]
|
|
446
|
+
:raises Exception: If the given plugin identifier is not declared in any
|
|
447
|
+
loaded plugin registry.
|
|
448
|
+
"""
|
|
243
449
|
ret = list()
|
|
244
450
|
for plugin_registry in self._plugin_registries:
|
|
245
451
|
if plugin_registry.has_plugin(plugin_id):
|
|
246
452
|
for layer_id in layer_ids:
|
|
247
|
-
layer
|
|
248
|
-
if layer is not None:
|
|
453
|
+
if layer := plugin_registry.get_layer(layer_id):
|
|
249
454
|
dp = layer.deploy_plugin(plugin_id,
|
|
250
455
|
src_jar,
|
|
251
456
|
interactive=interactive)
|
|
@@ -257,48 +462,136 @@ class TurtlesApp(object):
|
|
|
257
462
|
raise Exception(f'{src_jar}: {plugin_id} not declared in any plugin registry')
|
|
258
463
|
return ret
|
|
259
464
|
|
|
260
|
-
def _get_password(self) -> Optional[str]:
|
|
261
|
-
return self._password() if self._password else None
|
|
262
|
-
|
|
263
465
|
def _get_plugin_signing_alias(self) -> str:
|
|
466
|
+
"""
|
|
467
|
+
Returns the plugin signing alias from the loaded plugin signing
|
|
468
|
+
credentials.
|
|
469
|
+
|
|
470
|
+
:return: The plugin signing alias.
|
|
471
|
+
:rtype: str
|
|
472
|
+
"""
|
|
264
473
|
return self._plugin_signing_credentials.get_plugin_signing_alias()
|
|
265
474
|
|
|
266
475
|
def _get_plugin_signing_keystore(self) -> Path:
|
|
476
|
+
"""
|
|
477
|
+
Returns the plugin signing keystore file path from the loaded plugin
|
|
478
|
+
signing credentials.
|
|
479
|
+
|
|
480
|
+
:return: The plugin signing keystore file path.
|
|
481
|
+
:rtype: Path
|
|
482
|
+
"""
|
|
267
483
|
return self._plugin_signing_credentials.get_plugin_signing_keystore()
|
|
268
484
|
|
|
269
|
-
def _get_plugin_signing_password(self) -> str:
|
|
270
|
-
|
|
485
|
+
def _get_plugin_signing_password(self) -> Optional[Callable[[], str]]:
|
|
486
|
+
"""
|
|
487
|
+
Returns the plugin signing password.
|
|
488
|
+
|
|
489
|
+
:return: The plugin signing password callable.
|
|
490
|
+
:rtype: Optional[Callable[[], str]]
|
|
491
|
+
"""
|
|
492
|
+
return self._plugin_signing_password_callable
|
|
271
493
|
|
|
272
494
|
@staticmethod
|
|
273
495
|
def default_plugin_registry_catalog_choices() -> tuple[Path, ...]:
|
|
274
|
-
|
|
496
|
+
"""
|
|
497
|
+
Returns the tuple of default plugin registry catalog file choices.
|
|
498
|
+
|
|
499
|
+
See ``CONFIG_DIRS`` and ``PLUGIN_REGISTRY_CATALOG``.
|
|
500
|
+
|
|
501
|
+
:return: A tuple of default plugin registry catalog file choices.
|
|
502
|
+
:rtype: tuple[Path, ...]
|
|
503
|
+
"""
|
|
504
|
+
return Turtles._default_files(Turtles.PLUGIN_REGISTRY_CATALOG)
|
|
275
505
|
|
|
276
506
|
@staticmethod
|
|
277
507
|
def default_plugin_set_catalog_choices() -> tuple[Path, ...]:
|
|
278
|
-
|
|
508
|
+
"""
|
|
509
|
+
Returns the tuple of default plugin set catalog file choices.
|
|
510
|
+
|
|
511
|
+
See ``CONFIG_DIRS`` and ``PLUGIN_SET_CATALOG``.
|
|
512
|
+
|
|
513
|
+
:return: A tuple of default plugin set catalog file choices.
|
|
514
|
+
:rtype: tuple[Path, ...]
|
|
515
|
+
"""
|
|
516
|
+
return Turtles._default_files(Turtles.PLUGIN_SET_CATALOG)
|
|
279
517
|
|
|
280
518
|
@staticmethod
|
|
281
519
|
def default_plugin_signing_credentials_choices() -> tuple[Path, ...]:
|
|
282
|
-
|
|
520
|
+
"""
|
|
521
|
+
Returns the tuple of default plugin signing credentials file choices.
|
|
522
|
+
|
|
523
|
+
See ``CONFIG_DIRS`` and ``PLUGIN_SIGNING_CREDENTIALS``.
|
|
524
|
+
|
|
525
|
+
:return: A tuple of default plugin signing credentials file choices.
|
|
526
|
+
:rtype: tuple[Path, ...]
|
|
527
|
+
"""
|
|
528
|
+
return Turtles._default_files(Turtles.PLUGIN_SIGNING_CREDENTIALS)
|
|
283
529
|
|
|
284
530
|
@staticmethod
|
|
285
531
|
def select_default_plugin_registry_catalog() -> Optional[Path]:
|
|
286
|
-
|
|
532
|
+
"""
|
|
533
|
+
Of the default plugin registry catalog file choices, select the first
|
|
534
|
+
one that exists.
|
|
535
|
+
|
|
536
|
+
See ``default_plugin_registry_catalog_choices`` and ``_select_file``.
|
|
537
|
+
|
|
538
|
+
:return: The first of the default plugin registry catalog file choices
|
|
539
|
+
that exists, or None if none do.
|
|
540
|
+
:rtype: Optional[Path]
|
|
541
|
+
"""
|
|
542
|
+
return Turtles._select_file(Turtles.default_plugin_registry_catalog_choices())
|
|
287
543
|
|
|
288
544
|
@staticmethod
|
|
289
545
|
def select_default_plugin_set_catalog() -> Optional[Path]:
|
|
290
|
-
|
|
546
|
+
"""
|
|
547
|
+
Of the default plugin set catalog file choices, select the first one
|
|
548
|
+
that exists.
|
|
549
|
+
|
|
550
|
+
See ``default_plugin_registry_set_choices`` and ``_select_file``.
|
|
551
|
+
|
|
552
|
+
:return: The first of the default plugin set catalog file choices that
|
|
553
|
+
exists, or None if none do.
|
|
554
|
+
:rtype: Optional[Path]
|
|
555
|
+
"""
|
|
556
|
+
return Turtles._select_file(Turtles.default_plugin_set_catalog_choices())
|
|
291
557
|
|
|
292
558
|
@staticmethod
|
|
293
559
|
def select_default_plugin_signing_credentials() -> Optional[Path]:
|
|
294
|
-
|
|
560
|
+
"""
|
|
561
|
+
Of the default plugin signing credentials file choices, select the first
|
|
562
|
+
one that exists.
|
|
563
|
+
|
|
564
|
+
See ``default_plugin_registry_set_choices`` and ``_select_file``.
|
|
565
|
+
|
|
566
|
+
:return: The first of the default plugin signing credentials file
|
|
567
|
+
choices that exists, or None if none do.
|
|
568
|
+
:rtype: Optional[Path]
|
|
569
|
+
"""
|
|
570
|
+
return Turtles._select_file(Turtles.default_plugin_signing_credentials_choices())
|
|
295
571
|
|
|
296
572
|
@staticmethod
|
|
297
573
|
def _default_files(file_str) -> tuple[Path, ...]:
|
|
298
|
-
|
|
574
|
+
"""
|
|
575
|
+
Given a file base name, returns a tuple of this file in the various
|
|
576
|
+
Turtles configuration directories (``CONFIG_DIRS``).
|
|
577
|
+
|
|
578
|
+
:param file_str: A file base name.
|
|
579
|
+
:type file_str: str
|
|
580
|
+
:return: The file in the various Turtles configuration directories.
|
|
581
|
+
:rtype: tuple[Path, ...]
|
|
582
|
+
"""
|
|
583
|
+
return tuple(dir_path.joinpath(file_str) for dir_path in Turtles.CONFIG_DIRS)
|
|
299
584
|
|
|
300
585
|
@staticmethod
|
|
301
586
|
def _select_file(choices: Iterable[Path]) -> Optional[Path]:
|
|
587
|
+
"""
|
|
588
|
+
Of the given files, returns the first one that exists.
|
|
589
|
+
|
|
590
|
+
:param choices: An iterable of file paths.
|
|
591
|
+
:type choices: Iterable[Path]
|
|
592
|
+
:return: The first choice that exists, or None if none do.
|
|
593
|
+
:rtype: Optional[Path]
|
|
594
|
+
"""
|
|
302
595
|
for p in choices:
|
|
303
596
|
if p.is_file():
|
|
304
597
|
return p
|