lockss-turtles 0.6.0.dev21__py3-none-any.whl → 0.6.0.dev23__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/cli.py +12 -16
- lockss/turtles/plugin_registry.py +468 -44
- lockss/turtles/plugin_set.py +325 -79
- lockss/turtles/util.py +44 -3
- lockss_turtles-0.6.0.dev23.dist-info/METADATA +64 -0
- lockss_turtles-0.6.0.dev23.dist-info/RECORD +15 -0
- lockss_turtles-0.6.0.dev21.dist-info/METADATA +0 -1042
- lockss_turtles-0.6.0.dev21.dist-info/RECORD +0 -15
- {lockss_turtles-0.6.0.dev21.dist-info → lockss_turtles-0.6.0.dev23.dist-info}/LICENSE +0 -0
- {lockss_turtles-0.6.0.dev21.dist-info → lockss_turtles-0.6.0.dev23.dist-info}/WHEEL +0 -0
- {lockss_turtles-0.6.0.dev21.dist-info → lockss_turtles-0.6.0.dev23.dist-info}/entry_points.txt +0 -0
|
@@ -28,6 +28,10 @@
|
|
|
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
|
+
Library to represent plugin registries and plugin registry catalogs.
|
|
33
|
+
"""
|
|
34
|
+
|
|
31
35
|
# Remove in Python 3.14
|
|
32
36
|
# See https://stackoverflow.com/questions/33533148/how-do-i-type-hint-a-method-with-the-type-of-the-enclosing-class/33533514#33533514
|
|
33
37
|
from __future__ import annotations
|
|
@@ -45,66 +49,211 @@ from .plugin import Plugin, PluginIdentifier
|
|
|
45
49
|
from .util import BaseModelWithRoot
|
|
46
50
|
|
|
47
51
|
|
|
52
|
+
#: A type alias for the plugin registry catalog kind.
|
|
48
53
|
PluginRegistryCatalogKind = Literal['PluginRegistryCatalog']
|
|
49
54
|
|
|
50
55
|
|
|
51
56
|
class PluginRegistryCatalog(BaseModelWithRoot):
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
"""
|
|
58
|
+
A Pydantic model (``lockss.turtles.util.BaseModelWithRoot``) to represent a
|
|
59
|
+
plugin registry catalog.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
#: This object's kind.
|
|
63
|
+
kind: PluginRegistryCatalogKind = Field(title='Kind',
|
|
64
|
+
description="This object's kind")
|
|
65
|
+
|
|
66
|
+
#: A non-empty list of plugin registry files.
|
|
67
|
+
plugin_registry_files: list[str] = Field(alias='plugin-registry-files',
|
|
68
|
+
min_length=1,
|
|
69
|
+
title='Plugin Registry Files',
|
|
70
|
+
description="A non-empty list of plugin registry files")
|
|
54
71
|
|
|
55
72
|
def get_plugin_registry_files(self) -> list[Path]:
|
|
73
|
+
"""
|
|
74
|
+
Returns the list of plugin registry files in this catalog, relative to
|
|
75
|
+
the plugin registry catalog file if applicable.
|
|
76
|
+
|
|
77
|
+
:return: A non-null list of plugin registry file paths.
|
|
78
|
+
:rtype: list[Path]
|
|
79
|
+
"""
|
|
56
80
|
return [self.get_root().joinpath(pstr) for pstr in self.plugin_registry_files]
|
|
57
81
|
|
|
58
82
|
|
|
83
|
+
#: A type alias for the two plugin registry layout types.
|
|
59
84
|
PluginRegistryLayoutType = Literal['directory', 'rcs']
|
|
60
85
|
|
|
61
86
|
|
|
87
|
+
#: A type alias for the three plugin registry layout file naming conventions.
|
|
62
88
|
PluginRegistryLayoutFileNamingConvention = Literal['abbreviated', 'identifier', 'underscore']
|
|
63
89
|
|
|
64
90
|
|
|
65
91
|
class BasePluginRegistryLayout(BaseModel, ABC):
|
|
66
|
-
|
|
92
|
+
"""
|
|
93
|
+
An abstract Pydantic model (``lockss.turtles.util.BaseModelWithRoot``) to
|
|
94
|
+
represent a plugin registry layout, with concrete implementations
|
|
95
|
+
``DirectoryPluginRegistryLayout`` and ``RcsPluginRegistryLayout``.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
#: Pydantic definition of the ``type`` field.
|
|
99
|
+
TYPE_FIELD: ClassVar[dict[str, str]] = dict(title='Plugin Registry Layout Type',
|
|
100
|
+
description='A plugin registry layout type')
|
|
101
|
+
|
|
102
|
+
#: Default file naming convention.
|
|
67
103
|
FILE_NAMING_CONVENTION_DEFAULT: ClassVar[PluginRegistryLayoutFileNamingConvention] = 'identifier'
|
|
68
|
-
FILE_NAMING_CONVENTION_FIELD: ClassVar[dict[str, str]] = dict(alias='file-naming-convention', title='Plugin Registry Layout File Naming Convention', description='A file naming convention for the plugin registry layout')
|
|
69
104
|
|
|
105
|
+
#: Pydantic definition of the ``file_naming_convention`` field.
|
|
106
|
+
FILE_NAMING_CONVENTION_FIELD: ClassVar[dict[str, str]] = dict(alias='file-naming-convention',
|
|
107
|
+
title='Plugin Registry Layout File Naming Convention',
|
|
108
|
+
description='A file naming convention for the plugin registry layout')
|
|
109
|
+
|
|
110
|
+
#: Internal backreference to the enclosing plugin registry; see ``initialize``.
|
|
70
111
|
_plugin_registry: Optional[PluginRegistry]
|
|
71
112
|
|
|
72
|
-
def deploy_plugin(self,
|
|
113
|
+
def deploy_plugin(self,
|
|
114
|
+
plugin_id: PluginIdentifier,
|
|
115
|
+
layer: PluginRegistryLayer,
|
|
116
|
+
src_path: Path,
|
|
117
|
+
interactive: bool=False) -> Optional[tuple[Path, Plugin]]:
|
|
118
|
+
"""
|
|
119
|
+
Deploys the given plugin to the target plugin registry layer according to
|
|
120
|
+
this plugin registry layout's file naming convention.
|
|
121
|
+
|
|
122
|
+
See ``_copy_jar``.
|
|
123
|
+
|
|
124
|
+
:param plugin_id: A plugin identifier.
|
|
125
|
+
:type plugin_id: PluginIdentifier
|
|
126
|
+
:param layer: A plugin registry layer.
|
|
127
|
+
:type layer: PluginRegistryLayer
|
|
128
|
+
:param src_path: The path of the plugin JAR.
|
|
129
|
+
:type src_path: Path
|
|
130
|
+
:param interactive: If False (the default), no interactive confirmation
|
|
131
|
+
will occur. If True, and the given plugin is being
|
|
132
|
+
deployed to the target layer for the very first
|
|
133
|
+
time, the user will be prompted interactively to
|
|
134
|
+
confirm.
|
|
135
|
+
:type interactive: bool
|
|
136
|
+
:return: A tuple of the path of the deployed JAR and a Plugin object
|
|
137
|
+
instantiated from the source JAR, or None if the user was
|
|
138
|
+
prompted for confirmation and responded negatively.
|
|
139
|
+
:rtype: Optional[tuple[Path, Plugin]]
|
|
140
|
+
"""
|
|
73
141
|
src_path = path(src_path) # in case it's a string
|
|
74
142
|
dst_path = self._get_dstpath(plugin_id, layer)
|
|
75
143
|
if not self._proceed_copy(src_path, dst_path, layer, interactive=interactive):
|
|
76
144
|
return None
|
|
77
|
-
self._copy_jar(src_path, dst_path
|
|
145
|
+
self._copy_jar(src_path, dst_path)
|
|
78
146
|
return dst_path, Plugin.from_jar(src_path)
|
|
79
147
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
148
|
+
# Believed to be abandoned:
|
|
149
|
+
|
|
150
|
+
# def get_file_for(self,
|
|
151
|
+
# plugin_id,
|
|
152
|
+
# layer: PluginRegistryLayer) -> Optional[Path]:
|
|
153
|
+
# """
|
|
154
|
+
#
|
|
155
|
+
# :param plugin_id:
|
|
156
|
+
# :type plugin_id:
|
|
157
|
+
# :param layer:
|
|
158
|
+
# :type layer:
|
|
159
|
+
# :return:
|
|
160
|
+
# :rtype:
|
|
161
|
+
# """
|
|
162
|
+
# jar_path = self._get_dstpath(plugin_id, layer)
|
|
163
|
+
# return jar_path if jar_path.is_file() else None
|
|
83
164
|
|
|
84
165
|
def get_file_naming_convention(self) -> PluginRegistryLayoutFileNamingConvention:
|
|
166
|
+
"""
|
|
167
|
+
Returns the concrete implementation's ``file_naming`convention`` field.
|
|
168
|
+
|
|
169
|
+
:return: This plugin registry layout's file naming convention.
|
|
170
|
+
:rtype: PluginRegistryLayoutFileNamingConvention
|
|
171
|
+
"""
|
|
85
172
|
return getattr(self, 'file_naming_convention')
|
|
86
173
|
|
|
87
174
|
def get_plugin_registry(self) -> PluginRegistry:
|
|
175
|
+
"""
|
|
176
|
+
Returns the enclosing plugin registry.
|
|
177
|
+
|
|
178
|
+
See ``initialize``.
|
|
179
|
+
|
|
180
|
+
:return: The enclosing plugin registry.
|
|
181
|
+
:rtype: PluginRegistry
|
|
182
|
+
:raises ValueError: If ``initialize`` was not called on the object.
|
|
183
|
+
"""
|
|
88
184
|
if self._plugin_registry is None:
|
|
89
185
|
raise ValueError('Uninitialized plugin registry')
|
|
90
186
|
return self._plugin_registry
|
|
91
187
|
|
|
92
188
|
def get_type(self) -> PluginRegistryLayoutType:
|
|
189
|
+
"""
|
|
190
|
+
Returns the concrete implementation's ``type`` field.
|
|
191
|
+
|
|
192
|
+
:return: This plugin registry layout's type.
|
|
193
|
+
:rtype: PluginRegistryLayoutType
|
|
194
|
+
"""
|
|
93
195
|
return getattr(self, 'type')
|
|
94
196
|
|
|
95
|
-
def initialize(self,
|
|
197
|
+
def initialize(self,
|
|
198
|
+
plugin_registry: PluginRegistry) -> BasePluginRegistryLayout:
|
|
199
|
+
"""
|
|
200
|
+
Initializes the plugin registry backreference. Mandatory call after
|
|
201
|
+
object creation.
|
|
202
|
+
|
|
203
|
+
:param plugin_registry: The enclosing plugin registry.
|
|
204
|
+
:type plugin_registry: PluginRegistry
|
|
205
|
+
:return: This object (for chaining).
|
|
206
|
+
:rtype: BasePluginRegistryLayout
|
|
207
|
+
"""
|
|
96
208
|
self._plugin_registry = plugin_registry
|
|
97
209
|
return self
|
|
98
210
|
|
|
99
|
-
def model_post_init(self,
|
|
211
|
+
def model_post_init(self,
|
|
212
|
+
context: Any) -> None:
|
|
213
|
+
"""
|
|
214
|
+
Pydantic post-initialization method, to create the ``_plugin_registry``
|
|
215
|
+
backreference.
|
|
216
|
+
|
|
217
|
+
See ``initialize``.
|
|
218
|
+
|
|
219
|
+
:param context: The Pydantic context.
|
|
220
|
+
:type context: Any
|
|
221
|
+
"""
|
|
100
222
|
super().model_post_init(context)
|
|
101
223
|
self._plugin_registry = None
|
|
102
224
|
|
|
103
225
|
@abstractmethod
|
|
104
|
-
def _copy_jar(self,
|
|
226
|
+
def _copy_jar(self,
|
|
227
|
+
src_path: Path,
|
|
228
|
+
dst_path: Path) -> None:
|
|
229
|
+
"""
|
|
230
|
+
Implementation-specific copy of the plugin JAR from a source path to its
|
|
231
|
+
intended deployed path.
|
|
232
|
+
|
|
233
|
+
:param src_path: The path of the plugin JAR to be deployed.
|
|
234
|
+
:type src_path: Path
|
|
235
|
+
:param dst_path: The intended path of the deployed JAR.
|
|
236
|
+
:type dst_path: Path
|
|
237
|
+
"""
|
|
105
238
|
pass
|
|
106
239
|
|
|
107
|
-
def _get_dstfile(self,
|
|
240
|
+
def _get_dstfile(self,
|
|
241
|
+
plugin_id: PluginIdentifier) -> str:
|
|
242
|
+
"""
|
|
243
|
+
Computes the destination file name (not path) based on this layout's
|
|
244
|
+
file naming convention.
|
|
245
|
+
|
|
246
|
+
Implemented here because common to both concrete implementations.
|
|
247
|
+
|
|
248
|
+
:param plugin_id: A plugin identifier.
|
|
249
|
+
:type plugin_id: PluginIdentifier
|
|
250
|
+
:return: A file name string consistent with the file naming convention.
|
|
251
|
+
For a plugin identifier ``org.myproject.plugin.MyPlugin``, the
|
|
252
|
+
result is ``MyPlugin.jar`` for ``abbreviated``,
|
|
253
|
+
``org.myproject.plugin.MyPlugin.jar`` for ``identifier`` and
|
|
254
|
+
``org_myproject_plugin_MyPlugin.jar`` for ``underscore``.
|
|
255
|
+
:rtype: str
|
|
256
|
+
"""
|
|
108
257
|
if (conv := self.get_file_naming_convention()) == 'abbreviated':
|
|
109
258
|
return f'{plugin_id.split(".")[-1]}.jar'
|
|
110
259
|
elif conv == 'identifier':
|
|
@@ -114,10 +263,44 @@ class BasePluginRegistryLayout(BaseModel, ABC):
|
|
|
114
263
|
else:
|
|
115
264
|
raise InternalError()
|
|
116
265
|
|
|
117
|
-
def _get_dstpath(self,
|
|
266
|
+
def _get_dstpath(self,
|
|
267
|
+
plugin_id: PluginIdentifier,
|
|
268
|
+
layer: PluginRegistryLayer) -> Path:
|
|
269
|
+
"""
|
|
270
|
+
Computes the destination path for the given plugin being deployed to the
|
|
271
|
+
target layer, using the layout's file naming convention.
|
|
272
|
+
|
|
273
|
+
:param plugin_id: A plugin identifier.
|
|
274
|
+
:type plugin_id: PluginIdentifier
|
|
275
|
+
:param layer: A target plugin registry layer.
|
|
276
|
+
:type layer: PluginRegistryLayer
|
|
277
|
+
:return: The would-be destination of the deployed JAR.
|
|
278
|
+
:rtype: Path
|
|
279
|
+
"""
|
|
118
280
|
return layer.get_path().joinpath(self._get_dstfile(plugin_id))
|
|
119
281
|
|
|
120
|
-
def _proceed_copy(self,
|
|
282
|
+
def _proceed_copy(self,
|
|
283
|
+
src_path: Path,
|
|
284
|
+
dst_path: Path,
|
|
285
|
+
layer: PluginRegistryLayer,
|
|
286
|
+
interactive: bool=False) -> bool:
|
|
287
|
+
"""
|
|
288
|
+
Determines whether the copy of the JAR should proceed.
|
|
289
|
+
|
|
290
|
+
:param src_path: The path of the JAR being deployed.
|
|
291
|
+
:type src_path: Path
|
|
292
|
+
:param dst_path: The path of the intended deployed JAR.
|
|
293
|
+
:type dst_path: Path
|
|
294
|
+
:param layer: The target plugin registry layer.
|
|
295
|
+
:type layer: PluginRegistryLayer
|
|
296
|
+
:param interactive: Whether interactive prompts are allowed (False by
|
|
297
|
+
default)
|
|
298
|
+
:type interactive: bool
|
|
299
|
+
:return: True, unless the destination file does not exist yet, the
|
|
300
|
+
interactive flag is True, and the user does not respond
|
|
301
|
+
positively to the confirmation prompt.
|
|
302
|
+
:rtype: bool
|
|
303
|
+
"""
|
|
121
304
|
if not dst_path.exists():
|
|
122
305
|
if interactive:
|
|
123
306
|
i = input(f'{dst_path} does not exist in {self.get_plugin_registry().get_id()}:{layer.get_id()} ({layer.get_name()}); create it (y/n)? [n] ').lower() or 'n'
|
|
@@ -127,28 +310,74 @@ class BasePluginRegistryLayout(BaseModel, ABC):
|
|
|
127
310
|
|
|
128
311
|
|
|
129
312
|
class DirectoryPluginRegistryLayout(BasePluginRegistryLayout):
|
|
313
|
+
"""
|
|
314
|
+
A plugin registry layout that keeps plugin JARs in a single directory.
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
#: This plugin registry layout's type.
|
|
130
318
|
type: Literal['directory'] = Field(**BasePluginRegistryLayout.TYPE_FIELD)
|
|
131
|
-
file_naming_convention: Optional[PluginRegistryLayoutFileNamingConvention] = Field(BasePluginRegistryLayout.FILE_NAMING_CONVENTION_DEFAULT, **BasePluginRegistryLayout.FILE_NAMING_CONVENTION_FIELD)
|
|
132
319
|
|
|
133
|
-
|
|
134
|
-
|
|
320
|
+
#: This plugin registry layout's file naming convention.
|
|
321
|
+
file_naming_convention: Optional[PluginRegistryLayoutFileNamingConvention] = Field(BasePluginRegistryLayout.FILE_NAMING_CONVENTION_DEFAULT,
|
|
322
|
+
**BasePluginRegistryLayout.FILE_NAMING_CONVENTION_FIELD)
|
|
323
|
+
|
|
324
|
+
def _copy_jar(self,
|
|
325
|
+
src_path: Path,
|
|
326
|
+
dst_path: Path) -> None:
|
|
327
|
+
"""
|
|
328
|
+
Copies the plugin JAR from a source path to its intended deployed path.
|
|
329
|
+
|
|
330
|
+
Additionally, if SELinux is enabled, sets the type of the security
|
|
331
|
+
context of the deployed path to ``httpd_sys_content_t``.
|
|
332
|
+
|
|
333
|
+
:param src_path: The path of the plugin JAR to be deployed.
|
|
334
|
+
:type src_path: Path
|
|
335
|
+
:param dst_path: The intended path of the deployed JAR.
|
|
336
|
+
:type dst_path: Path
|
|
337
|
+
:raises subprocess.CalledProcessError: If an invoked subprocess fails.
|
|
338
|
+
"""
|
|
339
|
+
dst_dir, dst_file = dst_path.parent, dst_path.name
|
|
135
340
|
subprocess.run(['cp', str(src_path), str(dst_path)], check=True, cwd=dst_dir)
|
|
136
341
|
if subprocess.run('command -v selinuxenabled > /dev/null && selinuxenabled && command -v chcon > /dev/null', shell=True).returncode == 0:
|
|
137
|
-
cmd = ['chcon', '-t', 'httpd_sys_content_t',
|
|
342
|
+
cmd = ['chcon', '-t', 'httpd_sys_content_t', dst_file]
|
|
138
343
|
subprocess.run(cmd, check=True, cwd=dst_dir)
|
|
139
344
|
|
|
140
345
|
|
|
141
346
|
class RcsPluginRegistryLayout(DirectoryPluginRegistryLayout):
|
|
347
|
+
"""
|
|
348
|
+
A plugin registry layout that is like ``DirectoryPluginRegistryLayout`` but
|
|
349
|
+
also uses `GNU RCS <https://www.gnu.org/software/rcs/>`_ to keep a record of
|
|
350
|
+
successive plugin versions in an ``RCS`` subdirectory.
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
#: This plugin registry layout's type. Shadows that of ``DirectoryPluginRegistryLayout`` due to inheritance.
|
|
142
354
|
type: Literal['rcs'] = Field(**BasePluginRegistryLayout.TYPE_FIELD)
|
|
143
|
-
file_naming_convention: Optional[PluginRegistryLayoutFileNamingConvention] = Field(BasePluginRegistryLayout.FILE_NAMING_CONVENTION_DEFAULT, **BasePluginRegistryLayout.FILE_NAMING_CONVENTION_FIELD)
|
|
144
355
|
|
|
145
|
-
|
|
146
|
-
|
|
356
|
+
# Believed to be unnecessary:
|
|
357
|
+
|
|
358
|
+
#file_naming_convention: Optional[PluginRegistryLayoutFileNamingConvention] = Field(BasePluginRegistryLayout.FILE_NAMING_CONVENTION_DEFAULT, **BasePluginRegistryLayout.FILE_NAMING_CONVENTION_FIELD)
|
|
359
|
+
|
|
360
|
+
def _copy_jar(self,
|
|
361
|
+
src_path: Path,
|
|
362
|
+
dst_path: Path) -> None:
|
|
363
|
+
"""
|
|
364
|
+
Copies the plugin JAR from a source path to its intended deployed path.
|
|
365
|
+
|
|
366
|
+
Does ``co -l`` if applicable, does the same copy as the parent
|
|
367
|
+
``DirectoryPluginRegistryLayout._copy_jar``, then does ``ci -u``.
|
|
368
|
+
|
|
369
|
+
:param src_path: The path of the plugin JAR to be deployed.
|
|
370
|
+
:type src_path: Path
|
|
371
|
+
:param dst_path: The intended path of the deployed JAR.
|
|
372
|
+
:type dst_path: Path
|
|
373
|
+
:raises subprocess.CalledProcessError: If an invoked subprocess fails.
|
|
374
|
+
"""
|
|
375
|
+
dst_dir, dst_file = dst_path.parent, dst_path.name
|
|
147
376
|
plugin = Plugin.from_jar(src_path)
|
|
148
|
-
rcs_path = dst_dir.joinpath('RCS', f'{
|
|
377
|
+
rcs_path = dst_dir.joinpath('RCS', f'{dst_file},v')
|
|
149
378
|
# Maybe do co -l before the parent's copy
|
|
150
379
|
if dst_path.exists() and rcs_path.is_file():
|
|
151
|
-
cmd = ['co', '-l',
|
|
380
|
+
cmd = ['co', '-l', dst_file]
|
|
152
381
|
subprocess.run(cmd, check=True, cwd=dst_dir)
|
|
153
382
|
# Do the parent's copy
|
|
154
383
|
super()._copy_jar(src_path, dst_path)
|
|
@@ -156,101 +385,296 @@ class RcsPluginRegistryLayout(DirectoryPluginRegistryLayout):
|
|
|
156
385
|
cmd = ['ci', '-u', f'-mVersion {plugin.get_version()}']
|
|
157
386
|
if not rcs_path.is_file():
|
|
158
387
|
cmd.append(f'-t-{plugin.get_name()}')
|
|
159
|
-
cmd.append(
|
|
388
|
+
cmd.append(dst_file)
|
|
160
389
|
subprocess.run(cmd, check=True, cwd=dst_dir)
|
|
161
390
|
|
|
162
391
|
|
|
392
|
+
#: A type alias for plugin registry layouts, which is the union of
|
|
393
|
+
#: ``DirectoryPluginRegistryLayout`` and ``RcsPluginRegistryLayout`` using
|
|
394
|
+
#: ``type`` as the discriminator field.
|
|
163
395
|
PluginRegistryLayout = Annotated[Union[DirectoryPluginRegistryLayout, RcsPluginRegistryLayout], Field(discriminator='type')]
|
|
164
396
|
|
|
165
397
|
|
|
398
|
+
#: A type alias for plugin registry layer identifiers.
|
|
166
399
|
PluginRegistryLayerIdentifier = str
|
|
167
400
|
|
|
168
401
|
|
|
169
|
-
class PluginRegistryLayer(BaseModel
|
|
170
|
-
|
|
171
|
-
|
|
402
|
+
class PluginRegistryLayer(BaseModel):
|
|
403
|
+
"""
|
|
404
|
+
A Pydantic model to represent a plugin registry layer.
|
|
405
|
+
"""
|
|
172
406
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
407
|
+
#: This plugin registry layer's identifier.
|
|
408
|
+
id: PluginRegistryLayerIdentifier = Field(title='Plugin Registry Layer Identifier',
|
|
409
|
+
description='An identifier for the plugin registry layer')
|
|
176
410
|
|
|
411
|
+
#: This plugin registry layer's name.
|
|
412
|
+
name: str = Field(title='Plugin Registry Layer Name',
|
|
413
|
+
description='A name for the plugin registry layer')
|
|
414
|
+
|
|
415
|
+
#: This plugin registry layer's path.
|
|
416
|
+
path: str = Field(title='Plugin Registry Layer Path',
|
|
417
|
+
description='A root path for the plugin registry layer')
|
|
418
|
+
|
|
419
|
+
#: Internal backreference to the enclosing plugin registry; see ``initialize``.
|
|
177
420
|
_plugin_registry: Optional[PluginRegistry]
|
|
178
421
|
|
|
179
|
-
def deploy_plugin(self,
|
|
422
|
+
def deploy_plugin(self,
|
|
423
|
+
plugin_id: PluginIdentifier,
|
|
424
|
+
src_path: Path,
|
|
425
|
+
interactive: bool=False) -> Optional[tuple[Path, Plugin]]:
|
|
426
|
+
"""
|
|
427
|
+
Deploys the given plugin to this plugin registry layer according to
|
|
428
|
+
this plugin registry layout's file naming convention.
|
|
429
|
+
|
|
430
|
+
:param plugin_id: A plugin identifier.
|
|
431
|
+
:type plugin_id: PluginIdentifier
|
|
432
|
+
:param src_path: The path of the plugin JAR.
|
|
433
|
+
:type src_path: Path
|
|
434
|
+
:param interactive: If False (the default), no interactive confirmation
|
|
435
|
+
will occur. If True, and the given plugin is being
|
|
436
|
+
deployed to this layer for the very first time, the
|
|
437
|
+
user will be prompted interactively to confirm.
|
|
438
|
+
:type interactive: bool
|
|
439
|
+
:return: A tuple of the path of the deployed JAR and a Plugin object
|
|
440
|
+
instantiated from the source JAR, or None if the user was
|
|
441
|
+
prompted for confirmation and responded negatively.
|
|
442
|
+
:rtype: Optional[tuple[Path, Plugin]]
|
|
443
|
+
"""
|
|
180
444
|
return self.get_plugin_registry().get_layout().deploy_plugin(plugin_id, self, src_path, interactive)
|
|
181
445
|
|
|
182
446
|
def get_id(self) -> PluginRegistryLayerIdentifier:
|
|
447
|
+
"""
|
|
448
|
+
Returns this plugin registry layer's identifier.
|
|
449
|
+
|
|
450
|
+
:return: This plugin registry layer's identifier.
|
|
451
|
+
:rtype: PluginRegistryLayerIdentifier
|
|
452
|
+
"""
|
|
183
453
|
return self.id
|
|
184
454
|
|
|
185
455
|
def get_jars(self) -> list[Path]:
|
|
456
|
+
"""
|
|
457
|
+
Returns the list of this plugin registry layer's JAR file paths.
|
|
458
|
+
|
|
459
|
+
:return: A sorted list of JAR file paths.
|
|
460
|
+
:rtype: list[Path]
|
|
461
|
+
"""
|
|
462
|
+
# FIXME Strictly speaking this should be in the layout
|
|
186
463
|
return sorted(self.get_path().glob('*.jar'))
|
|
187
464
|
|
|
188
465
|
def get_name(self) -> str:
|
|
466
|
+
"""
|
|
467
|
+
Returns this plugin registry layer's name.
|
|
468
|
+
|
|
469
|
+
:return: This plugin registry layer's name.
|
|
470
|
+
:rtype: str
|
|
471
|
+
"""
|
|
189
472
|
return self.name
|
|
190
473
|
|
|
191
474
|
def get_path(self) -> Path:
|
|
475
|
+
"""
|
|
476
|
+
Returns this plugin registry layer's path.
|
|
477
|
+
|
|
478
|
+
:return: This plugin registry layer's path.
|
|
479
|
+
:rtype: Path
|
|
480
|
+
"""
|
|
192
481
|
return path(self.path)
|
|
193
482
|
|
|
194
483
|
def get_plugin_registry(self) -> PluginRegistry:
|
|
484
|
+
"""
|
|
485
|
+
Returns the enclosing plugin registry.
|
|
486
|
+
|
|
487
|
+
See ``initialize``.
|
|
488
|
+
|
|
489
|
+
:return: The enclosing plugin registry.
|
|
490
|
+
:rtype: PluginRegistry
|
|
491
|
+
:raises ValueError: If ``initialize`` was not called on the object.
|
|
492
|
+
"""
|
|
195
493
|
if self._plugin_registry is None:
|
|
196
494
|
raise ValueError('Uninitialized plugin registry')
|
|
197
495
|
return self._plugin_registry
|
|
198
496
|
|
|
199
|
-
def initialize(self,
|
|
497
|
+
def initialize(self,
|
|
498
|
+
plugin_registry: PluginRegistry) -> PluginRegistryLayer:
|
|
499
|
+
"""
|
|
500
|
+
Initializes the plugin registry backreference. Mandatory call after
|
|
501
|
+
object creation.
|
|
502
|
+
|
|
503
|
+
:param plugin_registry: The enclosing plugin registry.
|
|
504
|
+
:type plugin_registry: PluginRegistry
|
|
505
|
+
:return: This object (for chaining).
|
|
506
|
+
:rtype: BasePluginRegistryLayout
|
|
507
|
+
"""
|
|
200
508
|
self._plugin_registry = plugin_registry
|
|
201
509
|
return self
|
|
202
510
|
|
|
203
|
-
def model_post_init(self,
|
|
511
|
+
def model_post_init(self,
|
|
512
|
+
context: Any) -> None:
|
|
513
|
+
"""
|
|
514
|
+
Pydantic post-initialization method, to create the ``_plugin_registry``
|
|
515
|
+
backreference.
|
|
516
|
+
|
|
517
|
+
See ``initialize``.
|
|
518
|
+
|
|
519
|
+
:param context: The Pydantic context.
|
|
520
|
+
:type context: Any
|
|
521
|
+
"""
|
|
204
522
|
super().model_post_init(context)
|
|
205
523
|
self._plugin_registry = None
|
|
206
524
|
|
|
207
525
|
|
|
526
|
+
#: A type alias for the plugin registry kind.
|
|
208
527
|
PluginRegistryKind = Literal['PluginRegistry']
|
|
209
528
|
|
|
210
529
|
|
|
530
|
+
#: A type alias for plugin registry identifiers.
|
|
211
531
|
PluginRegistryIdentifier = str
|
|
212
532
|
|
|
213
533
|
|
|
214
534
|
class PluginRegistry(BaseModelWithRoot):
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
535
|
+
"""
|
|
536
|
+
A Pydantic model (``lockss.turtles.util.BaseModelWithRoot``) to represent a
|
|
537
|
+
plugin registry.
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
#: This object's kind.
|
|
541
|
+
kind: PluginRegistryKind = Field(title='Kind',
|
|
542
|
+
description="This object's kind")
|
|
543
|
+
|
|
544
|
+
#: This plugin registry's identifier.
|
|
545
|
+
id: PluginRegistryIdentifier = Field(title='Plugin Registry Identifier',
|
|
546
|
+
description='An identifier for the plugin set')
|
|
547
|
+
|
|
548
|
+
#: This plugin registry's name.
|
|
549
|
+
name: str = Field(title='Plugin Registry Name',
|
|
550
|
+
description='A name for the plugin set')
|
|
551
|
+
|
|
552
|
+
#: This plugin registry's layout.
|
|
553
|
+
layout: PluginRegistryLayout = Field(title='Plugin Registry Layout',
|
|
554
|
+
description='A layout for the plugin registry')
|
|
555
|
+
|
|
556
|
+
#: This plugin registry's layers.
|
|
557
|
+
layers: list[PluginRegistryLayer] = Field(min_length=1,
|
|
558
|
+
title='Plugin Registry Layers',
|
|
559
|
+
description="A non-empty list of plugin registry layers")
|
|
560
|
+
|
|
561
|
+
#: The plugin identifiers in this registry.
|
|
562
|
+
plugin_identifiers: list[PluginIdentifier] = Field(alias='plugin-identifiers',
|
|
563
|
+
min_length=1,
|
|
564
|
+
title='Plugin Identifiers',
|
|
565
|
+
description="A non-empty list of plugin identifiers")
|
|
566
|
+
|
|
567
|
+
#: The suppressed plugin identifiers, formerly in this plugin registry.
|
|
568
|
+
suppressed_plugin_identifiers: list[PluginIdentifier] = Field([],
|
|
569
|
+
alias='suppressed-plugin-identifiers',
|
|
570
|
+
title='Suppressed Plugin Identifiers',
|
|
571
|
+
description="A list of suppressed plugin identifiers")
|
|
222
572
|
|
|
223
573
|
def get_id(self) -> PluginRegistryIdentifier:
|
|
574
|
+
"""
|
|
575
|
+
Returns this plugin registry's identifier.
|
|
576
|
+
|
|
577
|
+
:return: This plugin registry's identifier.
|
|
578
|
+
:rtype: PluginRegistryIdentifier
|
|
579
|
+
"""
|
|
224
580
|
return self.id
|
|
225
581
|
|
|
226
|
-
def get_layer(self,
|
|
582
|
+
def get_layer(self,
|
|
583
|
+
layer_id: PluginRegistryLayerIdentifier) -> Optional[PluginRegistryLayer]:
|
|
584
|
+
"""
|
|
585
|
+
Returns the plugin registry layer with the given identifier.
|
|
586
|
+
|
|
587
|
+
:param layer_id: A plugin registry layer identifier.
|
|
588
|
+
:type layer_id: PluginRegistryLayerIdentifier
|
|
589
|
+
:return: The plugin registry layer from this registry with the given
|
|
590
|
+
identifier, or None if there is no such layer.
|
|
591
|
+
:rtype: Optional[PluginRegistryLayer]
|
|
592
|
+
"""
|
|
227
593
|
for layer in self.get_layers():
|
|
228
594
|
if layer.get_id() == layer_id:
|
|
229
595
|
return layer
|
|
230
596
|
return None
|
|
231
597
|
|
|
232
598
|
def get_layer_ids(self) -> list[PluginRegistryLayerIdentifier]:
|
|
599
|
+
"""
|
|
600
|
+
Returns a list of all the plugin registry layer identifiers in this
|
|
601
|
+
registry.
|
|
602
|
+
|
|
603
|
+
:return: A list of plugin registry layer identifiers.
|
|
604
|
+
:rtype: list[PluginRegistryLayerIdentifier]
|
|
605
|
+
"""
|
|
233
606
|
return [layer.get_id() for layer in self.get_layers()]
|
|
234
607
|
|
|
235
608
|
def get_layers(self) -> list[PluginRegistryLayer]:
|
|
609
|
+
"""
|
|
610
|
+
Returns a list of all the plugin registry layers in this registry.
|
|
611
|
+
|
|
612
|
+
:return: A list of plugin registry layers.
|
|
613
|
+
:rtype: list[PluginRegistryLayer]
|
|
614
|
+
"""
|
|
236
615
|
return self.layers
|
|
237
616
|
|
|
238
617
|
def get_layout(self) -> BasePluginRegistryLayout:
|
|
618
|
+
"""
|
|
619
|
+
Returns this plugin registry's layout.
|
|
620
|
+
|
|
621
|
+
:return: A list of plugin registry layers.
|
|
622
|
+
:rtype: list[PluginRegistryLayer]
|
|
623
|
+
"""
|
|
239
624
|
return self.layout
|
|
240
625
|
|
|
241
626
|
def get_name(self) -> str:
|
|
627
|
+
"""
|
|
628
|
+
Returns this plugin registry's name.
|
|
629
|
+
|
|
630
|
+
:return: This plugin registry's name.
|
|
631
|
+
:rtype: str
|
|
632
|
+
"""
|
|
242
633
|
return self.name
|
|
243
634
|
|
|
244
635
|
def get_plugin_identifiers(self) -> list[PluginIdentifier]:
|
|
636
|
+
"""
|
|
637
|
+
Returns the list of plugin identifiers in this registry.
|
|
638
|
+
|
|
639
|
+
:return: The list of plugin identifiers in this registry.
|
|
640
|
+
:rtype: list[PluginIdentifier]
|
|
641
|
+
"""
|
|
245
642
|
return self.plugin_identifiers
|
|
246
643
|
|
|
247
644
|
def get_suppressed_plugin_identifiers(self) -> list[PluginIdentifier]:
|
|
645
|
+
"""
|
|
646
|
+
Returns the list of suppressed plugin identifiers in this registry.
|
|
647
|
+
|
|
648
|
+
:return: The list of suppressed plugin identifiers in this registry.
|
|
649
|
+
:rtype: list[PluginIdentifier]
|
|
650
|
+
"""
|
|
248
651
|
return self.suppressed_plugin_identifiers
|
|
249
652
|
|
|
250
|
-
def has_plugin(self,
|
|
653
|
+
def has_plugin(self,
|
|
654
|
+
plugin_id: PluginIdentifier) -> bool:
|
|
655
|
+
"""
|
|
656
|
+
Determines if a given plugin identifier is in this registry.
|
|
657
|
+
|
|
658
|
+
:param plugin_id: A plugin identifier.
|
|
659
|
+
:type plugin_id: PluginIdentifier
|
|
660
|
+
:return: True if and only if the given plugin identifier is in this
|
|
661
|
+
registry.
|
|
662
|
+
:rtype: bool
|
|
663
|
+
"""
|
|
251
664
|
return plugin_id in self.get_plugin_identifiers()
|
|
252
665
|
|
|
253
|
-
def model_post_init(self,
|
|
666
|
+
def model_post_init(self,
|
|
667
|
+
context: Any) -> None:
|
|
668
|
+
"""
|
|
669
|
+
Pydantic post-initialization method to initialize the layout and all the
|
|
670
|
+
layers with this registry as the enclosing registry.
|
|
671
|
+
|
|
672
|
+
See ``BasePluginRegistrLayout.initialize`` and
|
|
673
|
+
``PluginRegistryLayout.initialize``.
|
|
674
|
+
|
|
675
|
+
:param context: The Pydantic context.
|
|
676
|
+
:type context: Any
|
|
677
|
+
"""
|
|
254
678
|
super().model_post_init(context)
|
|
255
679
|
self.get_layout().initialize(self)
|
|
256
680
|
for layer in self.get_layers():
|