lockss-turtles 0.6.0.dev20__py3-none-any.whl → 0.6.0.dev22__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 +4 -1
- lockss/turtles/cli.py +12 -16
- lockss/turtles/plugin.py +154 -10
- lockss/turtles/plugin_registry.py +468 -45
- lockss/turtles/plugin_set.py +325 -79
- lockss/turtles/util.py +44 -3
- {lockss_turtles-0.6.0.dev20.dist-info → lockss_turtles-0.6.0.dev22.dist-info}/METADATA +2 -2
- lockss_turtles-0.6.0.dev22.dist-info/RECORD +15 -0
- lockss_turtles-0.6.0.dev20.dist-info/RECORD +0 -15
- {lockss_turtles-0.6.0.dev20.dist-info → lockss_turtles-0.6.0.dev22.dist-info}/LICENSE +0 -0
- {lockss_turtles-0.6.0.dev20.dist-info → lockss_turtles-0.6.0.dev22.dist-info}/WHEEL +0 -0
- {lockss_turtles-0.6.0.dev20.dist-info → lockss_turtles-0.6.0.dev22.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,67 +49,211 @@ from .plugin import Plugin, PluginIdentifier
|
|
|
45
49
|
from .util import BaseModelWithRoot
|
|
46
50
|
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
#: A type alias for the plugin registry catalog kind.
|
|
49
53
|
PluginRegistryCatalogKind = Literal['PluginRegistryCatalog']
|
|
50
54
|
|
|
51
55
|
|
|
52
56
|
class PluginRegistryCatalog(BaseModelWithRoot):
|
|
53
|
-
|
|
54
|
-
|
|
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")
|
|
55
71
|
|
|
56
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
|
+
"""
|
|
57
80
|
return [self.get_root().joinpath(pstr) for pstr in self.plugin_registry_files]
|
|
58
81
|
|
|
59
82
|
|
|
83
|
+
#: A type alias for the two plugin registry layout types.
|
|
60
84
|
PluginRegistryLayoutType = Literal['directory', 'rcs']
|
|
61
85
|
|
|
62
86
|
|
|
87
|
+
#: A type alias for the three plugin registry layout file naming conventions.
|
|
63
88
|
PluginRegistryLayoutFileNamingConvention = Literal['abbreviated', 'identifier', 'underscore']
|
|
64
89
|
|
|
65
90
|
|
|
66
91
|
class BasePluginRegistryLayout(BaseModel, ABC):
|
|
67
|
-
|
|
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.
|
|
68
103
|
FILE_NAMING_CONVENTION_DEFAULT: ClassVar[PluginRegistryLayoutFileNamingConvention] = 'identifier'
|
|
69
|
-
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')
|
|
70
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``.
|
|
71
111
|
_plugin_registry: Optional[PluginRegistry]
|
|
72
112
|
|
|
73
|
-
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
|
+
"""
|
|
74
141
|
src_path = path(src_path) # in case it's a string
|
|
75
142
|
dst_path = self._get_dstpath(plugin_id, layer)
|
|
76
143
|
if not self._proceed_copy(src_path, dst_path, layer, interactive=interactive):
|
|
77
144
|
return None
|
|
78
|
-
self._copy_jar(src_path, dst_path
|
|
145
|
+
self._copy_jar(src_path, dst_path)
|
|
79
146
|
return dst_path, Plugin.from_jar(src_path)
|
|
80
147
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
84
164
|
|
|
85
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
|
+
"""
|
|
86
172
|
return getattr(self, 'file_naming_convention')
|
|
87
173
|
|
|
88
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
|
+
"""
|
|
89
184
|
if self._plugin_registry is None:
|
|
90
185
|
raise ValueError('Uninitialized plugin registry')
|
|
91
186
|
return self._plugin_registry
|
|
92
187
|
|
|
93
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
|
+
"""
|
|
94
195
|
return getattr(self, 'type')
|
|
95
196
|
|
|
96
|
-
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
|
+
"""
|
|
97
208
|
self._plugin_registry = plugin_registry
|
|
98
209
|
return self
|
|
99
210
|
|
|
100
|
-
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
|
+
"""
|
|
101
222
|
super().model_post_init(context)
|
|
102
223
|
self._plugin_registry = None
|
|
103
224
|
|
|
104
225
|
@abstractmethod
|
|
105
|
-
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
|
+
"""
|
|
106
238
|
pass
|
|
107
239
|
|
|
108
|
-
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
|
+
"""
|
|
109
257
|
if (conv := self.get_file_naming_convention()) == 'abbreviated':
|
|
110
258
|
return f'{plugin_id.split(".")[-1]}.jar'
|
|
111
259
|
elif conv == 'identifier':
|
|
@@ -115,10 +263,44 @@ class BasePluginRegistryLayout(BaseModel, ABC):
|
|
|
115
263
|
else:
|
|
116
264
|
raise InternalError()
|
|
117
265
|
|
|
118
|
-
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
|
+
"""
|
|
119
280
|
return layer.get_path().joinpath(self._get_dstfile(plugin_id))
|
|
120
281
|
|
|
121
|
-
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
|
+
"""
|
|
122
304
|
if not dst_path.exists():
|
|
123
305
|
if interactive:
|
|
124
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'
|
|
@@ -128,28 +310,74 @@ class BasePluginRegistryLayout(BaseModel, ABC):
|
|
|
128
310
|
|
|
129
311
|
|
|
130
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.
|
|
131
318
|
type: Literal['directory'] = Field(**BasePluginRegistryLayout.TYPE_FIELD)
|
|
132
|
-
file_naming_convention: Optional[PluginRegistryLayoutFileNamingConvention] = Field(BasePluginRegistryLayout.FILE_NAMING_CONVENTION_DEFAULT, **BasePluginRegistryLayout.FILE_NAMING_CONVENTION_FIELD)
|
|
133
319
|
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
136
340
|
subprocess.run(['cp', str(src_path), str(dst_path)], check=True, cwd=dst_dir)
|
|
137
341
|
if subprocess.run('command -v selinuxenabled > /dev/null && selinuxenabled && command -v chcon > /dev/null', shell=True).returncode == 0:
|
|
138
|
-
cmd = ['chcon', '-t', 'httpd_sys_content_t',
|
|
342
|
+
cmd = ['chcon', '-t', 'httpd_sys_content_t', dst_file]
|
|
139
343
|
subprocess.run(cmd, check=True, cwd=dst_dir)
|
|
140
344
|
|
|
141
345
|
|
|
142
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.
|
|
143
354
|
type: Literal['rcs'] = Field(**BasePluginRegistryLayout.TYPE_FIELD)
|
|
144
|
-
file_naming_convention: Optional[PluginRegistryLayoutFileNamingConvention] = Field(BasePluginRegistryLayout.FILE_NAMING_CONVENTION_DEFAULT, **BasePluginRegistryLayout.FILE_NAMING_CONVENTION_FIELD)
|
|
145
355
|
|
|
146
|
-
|
|
147
|
-
|
|
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
|
|
148
376
|
plugin = Plugin.from_jar(src_path)
|
|
149
|
-
rcs_path = dst_dir.joinpath('RCS', f'{
|
|
377
|
+
rcs_path = dst_dir.joinpath('RCS', f'{dst_file},v')
|
|
150
378
|
# Maybe do co -l before the parent's copy
|
|
151
379
|
if dst_path.exists() and rcs_path.is_file():
|
|
152
|
-
cmd = ['co', '-l',
|
|
380
|
+
cmd = ['co', '-l', dst_file]
|
|
153
381
|
subprocess.run(cmd, check=True, cwd=dst_dir)
|
|
154
382
|
# Do the parent's copy
|
|
155
383
|
super()._copy_jar(src_path, dst_path)
|
|
@@ -157,101 +385,296 @@ class RcsPluginRegistryLayout(DirectoryPluginRegistryLayout):
|
|
|
157
385
|
cmd = ['ci', '-u', f'-mVersion {plugin.get_version()}']
|
|
158
386
|
if not rcs_path.is_file():
|
|
159
387
|
cmd.append(f'-t-{plugin.get_name()}')
|
|
160
|
-
cmd.append(
|
|
388
|
+
cmd.append(dst_file)
|
|
161
389
|
subprocess.run(cmd, check=True, cwd=dst_dir)
|
|
162
390
|
|
|
163
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.
|
|
164
395
|
PluginRegistryLayout = Annotated[Union[DirectoryPluginRegistryLayout, RcsPluginRegistryLayout], Field(discriminator='type')]
|
|
165
396
|
|
|
166
397
|
|
|
398
|
+
#: A type alias for plugin registry layer identifiers.
|
|
167
399
|
PluginRegistryLayerIdentifier = str
|
|
168
400
|
|
|
169
401
|
|
|
170
|
-
class PluginRegistryLayer(BaseModel
|
|
171
|
-
|
|
172
|
-
|
|
402
|
+
class PluginRegistryLayer(BaseModel):
|
|
403
|
+
"""
|
|
404
|
+
A Pydantic model to represent a plugin registry layer.
|
|
405
|
+
"""
|
|
173
406
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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')
|
|
177
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``.
|
|
178
420
|
_plugin_registry: Optional[PluginRegistry]
|
|
179
421
|
|
|
180
|
-
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
|
+
"""
|
|
181
444
|
return self.get_plugin_registry().get_layout().deploy_plugin(plugin_id, self, src_path, interactive)
|
|
182
445
|
|
|
183
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
|
+
"""
|
|
184
453
|
return self.id
|
|
185
454
|
|
|
186
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
|
|
187
463
|
return sorted(self.get_path().glob('*.jar'))
|
|
188
464
|
|
|
189
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
|
+
"""
|
|
190
472
|
return self.name
|
|
191
473
|
|
|
192
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
|
+
"""
|
|
193
481
|
return path(self.path)
|
|
194
482
|
|
|
195
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
|
+
"""
|
|
196
493
|
if self._plugin_registry is None:
|
|
197
494
|
raise ValueError('Uninitialized plugin registry')
|
|
198
495
|
return self._plugin_registry
|
|
199
496
|
|
|
200
|
-
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
|
+
"""
|
|
201
508
|
self._plugin_registry = plugin_registry
|
|
202
509
|
return self
|
|
203
510
|
|
|
204
|
-
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
|
+
"""
|
|
205
522
|
super().model_post_init(context)
|
|
206
523
|
self._plugin_registry = None
|
|
207
524
|
|
|
208
525
|
|
|
526
|
+
#: A type alias for the plugin registry kind.
|
|
209
527
|
PluginRegistryKind = Literal['PluginRegistry']
|
|
210
528
|
|
|
211
529
|
|
|
530
|
+
#: A type alias for plugin registry identifiers.
|
|
212
531
|
PluginRegistryIdentifier = str
|
|
213
532
|
|
|
214
533
|
|
|
215
534
|
class PluginRegistry(BaseModelWithRoot):
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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")
|
|
223
572
|
|
|
224
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
|
+
"""
|
|
225
580
|
return self.id
|
|
226
581
|
|
|
227
|
-
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
|
+
"""
|
|
228
593
|
for layer in self.get_layers():
|
|
229
594
|
if layer.get_id() == layer_id:
|
|
230
595
|
return layer
|
|
231
596
|
return None
|
|
232
597
|
|
|
233
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
|
+
"""
|
|
234
606
|
return [layer.get_id() for layer in self.get_layers()]
|
|
235
607
|
|
|
236
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
|
+
"""
|
|
237
615
|
return self.layers
|
|
238
616
|
|
|
239
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
|
+
"""
|
|
240
624
|
return self.layout
|
|
241
625
|
|
|
242
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
|
+
"""
|
|
243
633
|
return self.name
|
|
244
634
|
|
|
245
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
|
+
"""
|
|
246
642
|
return self.plugin_identifiers
|
|
247
643
|
|
|
248
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
|
+
"""
|
|
249
651
|
return self.suppressed_plugin_identifiers
|
|
250
652
|
|
|
251
|
-
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
|
+
"""
|
|
252
664
|
return plugin_id in self.get_plugin_identifiers()
|
|
253
665
|
|
|
254
|
-
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
|
+
"""
|
|
255
678
|
super().model_post_init(context)
|
|
256
679
|
self.get_layout().initialize(self)
|
|
257
680
|
for layer in self.get_layers():
|