lockss-turtles 0.6.0.dev13__tar.gz → 0.6.0.dev15__tar.gz
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-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/PKG-INFO +3 -2
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/pyproject.toml +3 -2
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/src/lockss/turtles/__init__.py +1 -1
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/src/lockss/turtles/app.py +75 -15
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/src/lockss/turtles/cli.py +70 -10
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/src/lockss/turtles/plugin_registry.py +1 -1
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/CHANGELOG.rst +0 -0
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/LICENSE +0 -0
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/README.rst +0 -0
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/src/lockss/turtles/__main__.py +0 -0
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/src/lockss/turtles/plugin.py +0 -0
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/src/lockss/turtles/plugin_set.py +0 -0
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/src/lockss/turtles/util.py +0 -0
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/tests/unittest/lockss/turtles/__init__.py +0 -0
- {lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/tests/unittest/lockss/turtles/test_plugin_set.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: lockss-turtles
|
|
3
|
-
Version: 0.6.0.
|
|
3
|
+
Version: 0.6.0.dev15
|
|
4
4
|
Summary: Library and command line tool to manage LOCKSS plugin sets and LOCKSS plugin registries
|
|
5
5
|
License: BSD-3-Clause
|
|
6
6
|
Author: Thib Guicherd-Callin
|
|
@@ -18,9 +18,10 @@ Classifier: Programming Language :: Python
|
|
|
18
18
|
Classifier: Topic :: Software Development :: Libraries
|
|
19
19
|
Classifier: Topic :: System :: Archiving
|
|
20
20
|
Classifier: Topic :: Utilities
|
|
21
|
+
Requires-Dist: exceptiongroup (>=1.3.0,<1.4.0)
|
|
21
22
|
Requires-Dist: java-manifest (>=1.1.0,<1.2.0)
|
|
22
23
|
Requires-Dist: lockss-pybasic (>=0.1.0,<0.2.0)
|
|
23
|
-
Requires-Dist: pydantic (>=2.11.0,<
|
|
24
|
+
Requires-Dist: pydantic (>=2.11.0,<2.12.0)
|
|
24
25
|
Requires-Dist: pyyaml (>=6.0.0,<6.1.0)
|
|
25
26
|
Requires-Dist: xdg (>=6.0.0,<6.1.0)
|
|
26
27
|
Project-URL: Documentation, https://docs.lockss.org/en/latest/software/turtles
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
[project]
|
|
30
30
|
name = "lockss-turtles"
|
|
31
|
-
version = "0.6.0-
|
|
31
|
+
version = "0.6.0-dev15" # Always change in __init__.py, and at release time in README.rst and CHANGELOG.rst
|
|
32
32
|
description = "Library and command line tool to manage LOCKSS plugin sets and LOCKSS plugin registries"
|
|
33
33
|
license = { text = "BSD-3-Clause" }
|
|
34
34
|
readme = "README.rst"
|
|
@@ -40,11 +40,12 @@ maintainers = [
|
|
|
40
40
|
{ name = "Thib Guicherd-Callin", email = "thib@cs.stanford.edu" }
|
|
41
41
|
]
|
|
42
42
|
dependencies = [
|
|
43
|
+
"exceptiongroup (>=1.3.0,<1.4.0)",
|
|
43
44
|
"java-manifest (>=1.1.0,<1.2.0)",
|
|
44
45
|
"lockss-pybasic (>=0.1.0,<0.2.0)",
|
|
45
46
|
"pyyaml (>=6.0.0,<6.1.0)",
|
|
46
47
|
"xdg (>=6.0.0,<6.1.0)",
|
|
47
|
-
"pydantic (>=2.11.0,<
|
|
48
|
+
"pydantic (>=2.11.0,<2.12.0)",
|
|
48
49
|
]
|
|
49
50
|
classifiers = [
|
|
50
51
|
"Development Status :: 5 - Production/Stable",
|
|
@@ -36,10 +36,11 @@ from collections.abc import Callable, Iterable
|
|
|
36
36
|
from pathlib import Path
|
|
37
37
|
from typing import ClassVar, Literal, Optional, Union
|
|
38
38
|
|
|
39
|
-
import
|
|
39
|
+
from exceptiongroup import ExceptionGroup
|
|
40
40
|
from lockss.pybasic.fileutil import path
|
|
41
|
-
from pydantic import
|
|
41
|
+
from pydantic import Field, ValidationError
|
|
42
42
|
import xdg
|
|
43
|
+
import yaml
|
|
43
44
|
|
|
44
45
|
from .plugin import Plugin, PluginIdentifier
|
|
45
46
|
from .plugin_registry import PluginRegistry, PluginRegistryCatalog, PluginRegistryCatalogKind, PluginRegistryKind, PluginRegistryLayerIdentifier
|
|
@@ -104,56 +105,115 @@ class TurtlesApp(object):
|
|
|
104
105
|
plugin_registry_path = path(plugin_registry_path_or_str)
|
|
105
106
|
if plugin_registry_path in map(lambda pr: pr.get_root(), self._plugin_registries):
|
|
106
107
|
raise ValueError(f'Plugin registries already loaded from: {plugin_registry_path!s}')
|
|
108
|
+
errs, at_least_one = [], False
|
|
107
109
|
with plugin_registry_path.open('r') as fpr:
|
|
108
110
|
for yaml_obj in yaml.safe_load_all(fpr):
|
|
109
111
|
if isinstance(yaml_obj, dict) and yaml_obj.get('kind') in PluginRegistryKind.__args__:
|
|
110
|
-
|
|
111
|
-
|
|
112
|
+
try:
|
|
113
|
+
plugin_registry = PluginRegistry(**yaml_obj).initialize(plugin_registry_path.parent)
|
|
114
|
+
self._plugin_registries.append(plugin_registry)
|
|
115
|
+
at_least_one = True
|
|
116
|
+
except ValidationError as ve:
|
|
117
|
+
errs.append(ve)
|
|
118
|
+
if errs:
|
|
119
|
+
raise ExceptionGroup(f'Errors while loading plugin registries from: {plugin_registry_path!s}', errs)
|
|
120
|
+
if not at_least_one:
|
|
121
|
+
raise ValueError(f'No plugin registries found in: {plugin_registry_path!s}')
|
|
112
122
|
return self
|
|
113
123
|
|
|
114
124
|
def load_plugin_registry_catalogs(self, plugin_registry_catalog_path_or_str: PathOrStr) -> TurtlesApp:
|
|
115
125
|
plugin_registry_catalog_path = path(plugin_registry_catalog_path_or_str)
|
|
116
126
|
if plugin_registry_catalog_path in map(lambda prc: prc.get_root(), self._plugin_registry_catalogs):
|
|
117
127
|
raise ValueError(f'Plugin registry catalogs already loaded from: {plugin_registry_catalog_path!s}')
|
|
128
|
+
errs, at_least_one = [], False
|
|
118
129
|
with plugin_registry_catalog_path.open('r') as fprc:
|
|
119
130
|
for yaml_obj in yaml.safe_load_all(fprc):
|
|
120
131
|
if isinstance(yaml_obj, dict) and yaml_obj.get('kind') in PluginRegistryCatalogKind.__args__:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
132
|
+
try:
|
|
133
|
+
plugin_registry_catalog = PluginRegistryCatalog(**yaml_obj).initialize(plugin_registry_catalog_path.parent)
|
|
134
|
+
self._plugin_registry_catalogs.append(plugin_registry_catalog)
|
|
135
|
+
at_least_one = True
|
|
136
|
+
for plugin_registry_file in plugin_registry_catalog.get_plugin_registry_files():
|
|
137
|
+
try:
|
|
138
|
+
self.load_plugin_registries(plugin_registry_catalog_path.joinpath(plugin_registry_file))
|
|
139
|
+
except ValueError as ve:
|
|
140
|
+
errs.append(ve)
|
|
141
|
+
except ExceptionGroup as eg:
|
|
142
|
+
errs.extend(eg.exceptions)
|
|
143
|
+
except ValidationError as ve:
|
|
144
|
+
errs.append(ve)
|
|
145
|
+
if errs:
|
|
146
|
+
raise ExceptionGroup(f'Errors while loading plugin registry catalogs from: {plugin_registry_catalog_path!s}', errs)
|
|
147
|
+
if not at_least_one:
|
|
148
|
+
raise ValueError(f'No plugin registry catalogs found in: {plugin_registry_catalog_path!s}')
|
|
125
149
|
return self
|
|
126
150
|
|
|
127
151
|
def load_plugin_set_catalogs(self, plugin_set_catalog_path_or_str: PathOrStr) -> TurtlesApp:
|
|
128
152
|
plugin_set_catalog_path = path(plugin_set_catalog_path_or_str)
|
|
129
153
|
if plugin_set_catalog_path in map(lambda psc: psc.get_root(), self._plugin_set_catalogs):
|
|
130
154
|
raise ValueError(f'Plugin set catalogs already loaded from: {plugin_set_catalog_path!s}')
|
|
155
|
+
errs, at_least_one = [], False
|
|
131
156
|
with plugin_set_catalog_path.open('r') as fpsc:
|
|
132
157
|
for yaml_obj in yaml.safe_load_all(fpsc):
|
|
133
158
|
if isinstance(yaml_obj, dict) and yaml_obj.get('kind') in PluginSetCatalogKind.__args__:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
159
|
+
try:
|
|
160
|
+
plugin_set_catalog = PluginSetCatalog(**yaml_obj).initialize(plugin_set_catalog_path.parent)
|
|
161
|
+
self._plugin_set_catalogs.append(plugin_set_catalog)
|
|
162
|
+
at_least_one = True
|
|
163
|
+
for plugin_set_file in plugin_set_catalog.get_plugin_set_files():
|
|
164
|
+
try:
|
|
165
|
+
self.load_plugin_sets(plugin_set_catalog_path.joinpath(plugin_set_file))
|
|
166
|
+
except ValueError as ve:
|
|
167
|
+
errs.append(ve)
|
|
168
|
+
except ExceptionGroup as eg:
|
|
169
|
+
errs.extend(eg.exceptions)
|
|
170
|
+
except ValidationError as ve:
|
|
171
|
+
errs.append(ve)
|
|
172
|
+
if errs:
|
|
173
|
+
raise ExceptionGroup(f'Errors while loading plugin set catalogs from: {plugin_set_catalog_path!s}', errs)
|
|
174
|
+
if not at_least_one:
|
|
175
|
+
raise ValueError(f'No plugin set catalogs found in: {plugin_set_catalog_path!s}')
|
|
138
176
|
return self
|
|
139
177
|
|
|
140
178
|
def load_plugin_sets(self, plugin_set_path_or_str: PathOrStr) -> TurtlesApp:
|
|
141
179
|
plugin_set_path = path(plugin_set_path_or_str)
|
|
142
180
|
if plugin_set_path in map(lambda ps: ps.get_root(), self._plugin_sets):
|
|
143
181
|
raise ValueError(f'Plugin sets already loaded from: {plugin_set_path!s}')
|
|
182
|
+
errs, at_least_one = [], False
|
|
144
183
|
with plugin_set_path.open('r') as fps:
|
|
145
184
|
for yaml_obj in yaml.safe_load_all(fps):
|
|
146
185
|
if isinstance(yaml_obj, dict) and yaml_obj.get('kind') in PluginSetKind.__args__:
|
|
147
|
-
|
|
148
|
-
|
|
186
|
+
try:
|
|
187
|
+
plugin_set = PluginSet(**yaml_obj).initialize(plugin_set_path.parent)
|
|
188
|
+
self._plugin_sets.append(plugin_set)
|
|
189
|
+
at_least_one = True
|
|
190
|
+
except ValidationError as ve:
|
|
191
|
+
errs.append(ve)
|
|
192
|
+
if errs:
|
|
193
|
+
raise ExceptionGroup(f'Errors while loading plugin sets from: {plugin_set_path!s}', errs)
|
|
194
|
+
if not at_least_one:
|
|
195
|
+
raise ValueError(f'No plugin sets found in: {plugin_set_path!s}')
|
|
149
196
|
return self
|
|
150
197
|
|
|
151
198
|
def load_plugin_signing_credentials(self, plugin_signing_credentials_path_or_str: PathOrStr) -> TurtlesApp:
|
|
152
199
|
plugin_signing_credentials_path = path(plugin_signing_credentials_path_or_str)
|
|
153
200
|
if self._plugin_signing_credentials:
|
|
154
201
|
raise ValueError(f'Plugin signing credentials already loaded from: {self._plugin_signing_credentials.get_root()!s}')
|
|
202
|
+
found = 0
|
|
155
203
|
with plugin_signing_credentials_path.open('r') as fpsc:
|
|
156
|
-
|
|
204
|
+
for yaml_obj in yaml.safe_load_all(fpsc):
|
|
205
|
+
if isinstance(yaml_obj, dict) and yaml_obj.get('kind') in PluginSigningCredentialsKind.__args__:
|
|
206
|
+
found = found + 1
|
|
207
|
+
if not self._plugin_signing_credentials:
|
|
208
|
+
try:
|
|
209
|
+
plugin_signing_credentials = PluginSigningCredentials(**yaml.safe_load(fpsc)).initialize(plugin_signing_credentials_path.parent)
|
|
210
|
+
self._plugin_signing_credentials = plugin_signing_credentials
|
|
211
|
+
except ValidationError as ve:
|
|
212
|
+
raise ExceptionGroup(f'Errors while loading plugin signing credentials from: {plugin_signing_credentials_path!s}', [ve])
|
|
213
|
+
if found == 0:
|
|
214
|
+
raise ValueError(f'No plugin signing credentials found in: {plugin_signing_credentials_path!s}')
|
|
215
|
+
if found > 1:
|
|
216
|
+
raise ValueError(f'Multiple plugin signing credentials found in: {plugin_signing_credentials_path!s}')
|
|
157
217
|
return self
|
|
158
218
|
|
|
159
219
|
def release_plugin(self, plugin_ids: list[PluginIdentifier], layer_ids: list[PluginRegistryLayerIdentifier], interactive: bool=False) -> dict[str, list[tuple[str, str, Path, Plugin]]]:
|
|
@@ -35,6 +35,7 @@ Tool for managing LOCKSS plugin sets and LOCKSS plugin registries
|
|
|
35
35
|
from getpass import getpass
|
|
36
36
|
from pathlib import Path
|
|
37
37
|
|
|
38
|
+
from exceptiongroup import ExceptionGroup
|
|
38
39
|
from lockss.pybasic.cliutil import BaseCli, StringCommand, COPYRIGHT_DESCRIPTION, LICENSE_DESCRIPTION, VERSION_DESCRIPTION
|
|
39
40
|
from lockss.pybasic.fileutil import file_lines, path
|
|
40
41
|
from lockss.pybasic.outpututil import OutputFormatOptions
|
|
@@ -239,11 +240,29 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
|
239
240
|
return self._build_plugin(build_plugin_command)
|
|
240
241
|
|
|
241
242
|
def _build_plugin(self, build_plugin_command: BuildPluginCommand) -> None:
|
|
243
|
+
errs = []
|
|
242
244
|
for psc in build_plugin_command.get_plugin_set_catalogs():
|
|
243
|
-
|
|
245
|
+
try:
|
|
246
|
+
self._app.load_plugin_set_catalogs(psc)
|
|
247
|
+
except ValueError as ve:
|
|
248
|
+
errs.append(ve)
|
|
249
|
+
except ExceptionGroup as eg:
|
|
250
|
+
errs.extend(eg.exceptions)
|
|
244
251
|
for ps in build_plugin_command.get_plugin_sets():
|
|
245
|
-
|
|
246
|
-
|
|
252
|
+
try:
|
|
253
|
+
self._app.load_plugin_sets(ps)
|
|
254
|
+
except ValueError as ve:
|
|
255
|
+
errs.append(ve)
|
|
256
|
+
except ExceptionGroup as eg:
|
|
257
|
+
errs.extend(eg.exceptions)
|
|
258
|
+
try:
|
|
259
|
+
self._app.load_plugin_signing_credentials(build_plugin_command.get_plugin_signing_credentials())
|
|
260
|
+
except ValueError as ve:
|
|
261
|
+
errs.append(ve)
|
|
262
|
+
except ExceptionGroup as eg:
|
|
263
|
+
errs.extend(eg.exceptions)
|
|
264
|
+
if errs:
|
|
265
|
+
raise ExceptionGroup(f'Errors while setting up the environment for building plugins', errs)
|
|
247
266
|
self._obtain_password(build_plugin_command, non_interactive=build_plugin_command.non_interactive)
|
|
248
267
|
# Action
|
|
249
268
|
# ... plugin_id -> (set_id, jar_path, plugin)
|
|
@@ -257,10 +276,23 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
|
257
276
|
self._do_string_command(string_command)
|
|
258
277
|
|
|
259
278
|
def _deploy_plugin(self, deploy_plugin_command: DeployPluginCommand) -> None:
|
|
279
|
+
errs = []
|
|
260
280
|
for prc in deploy_plugin_command.get_plugin_registry_catalogs():
|
|
261
|
-
|
|
281
|
+
try:
|
|
282
|
+
self._app.load_plugin_registry_catalogs(prc)
|
|
283
|
+
except ValueError as ve:
|
|
284
|
+
errs.append(ve)
|
|
285
|
+
except ExceptionGroup as eg:
|
|
286
|
+
errs.extend(eg.exceptions)
|
|
262
287
|
for pr in deploy_plugin_command.get_plugin_registries():
|
|
263
|
-
|
|
288
|
+
try:
|
|
289
|
+
self._app.load_plugin_registries(pr)
|
|
290
|
+
except ValueError as ve:
|
|
291
|
+
errs.append(ve)
|
|
292
|
+
except ExceptionGroup as eg:
|
|
293
|
+
errs.extend(eg.exceptions)
|
|
294
|
+
if errs:
|
|
295
|
+
raise ExceptionGroup(f'Errors while setting up the environment for deploying plugins', errs)
|
|
264
296
|
# Action
|
|
265
297
|
# ... (src_path, plugin_id) -> list of (registry_id, layer_id, dst_path, plugin)
|
|
266
298
|
ret = self._app.deploy_plugin(deploy_plugin_command.get_plugin_jars(),
|
|
@@ -290,15 +322,43 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
|
290
322
|
self._app.set_password(lambda: _p)
|
|
291
323
|
|
|
292
324
|
def _release_plugin(self, release_plugin_command: ReleasePluginCommand) -> None:
|
|
325
|
+
errs = []
|
|
293
326
|
for psc in release_plugin_command.get_plugin_set_catalogs():
|
|
294
|
-
|
|
327
|
+
try:
|
|
328
|
+
self._app.load_plugin_set_catalogs(psc)
|
|
329
|
+
except ValueError as ve:
|
|
330
|
+
errs.append(ve)
|
|
331
|
+
except ExceptionGroup as eg:
|
|
332
|
+
errs.extend(eg.exceptions)
|
|
295
333
|
for ps in release_plugin_command.get_plugin_sets():
|
|
296
|
-
|
|
334
|
+
try:
|
|
335
|
+
self._app.load_plugin_sets(ps)
|
|
336
|
+
except ValueError as ve:
|
|
337
|
+
errs.append(ve)
|
|
338
|
+
except ExceptionGroup as eg:
|
|
339
|
+
errs.extend(eg.exceptions)
|
|
297
340
|
for prc in release_plugin_command.get_plugin_registry_catalogs():
|
|
298
|
-
|
|
341
|
+
try:
|
|
342
|
+
self._app.load_plugin_registry_catalogs(prc)
|
|
343
|
+
except ValueError as ve:
|
|
344
|
+
errs.append(ve)
|
|
345
|
+
except ExceptionGroup as eg:
|
|
346
|
+
errs.extend(eg.exceptions)
|
|
299
347
|
for pr in release_plugin_command.get_plugin_registries():
|
|
300
|
-
|
|
301
|
-
|
|
348
|
+
try:
|
|
349
|
+
self._app.load_plugin_registries(pr)
|
|
350
|
+
except ValueError as ve:
|
|
351
|
+
errs.append(ve)
|
|
352
|
+
except ExceptionGroup as eg:
|
|
353
|
+
errs.extend(eg.exceptions)
|
|
354
|
+
try:
|
|
355
|
+
self._app.load_plugin_signing_credentials(release_plugin_command.get_plugin_signing_credentials())
|
|
356
|
+
except ValueError as ve:
|
|
357
|
+
errs.append(ve)
|
|
358
|
+
except ExceptionGroup as eg:
|
|
359
|
+
errs.extend(eg.exceptions)
|
|
360
|
+
if errs:
|
|
361
|
+
raise ExceptionGroup(f'Errors while setting up the environment for deploying plugins', errs)
|
|
302
362
|
self._obtain_password(release_plugin_command, non_interactive=release_plugin_command.non_interactive)
|
|
303
363
|
# Action
|
|
304
364
|
# ... plugin_id -> list of (registry_id, layer_id, dst_path, plugin)
|
{lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/src/lockss/turtles/plugin_registry.py
RENAMED
|
@@ -146,7 +146,7 @@ class RcsPluginRegistryLayout(DirectoryPluginRegistryLayout):
|
|
|
146
146
|
def _copy_jar(self, src_path: Path, dst_path: Path, interactive: bool=False) -> None:
|
|
147
147
|
dst_dir, basename = dst_path.parent, dst_path.name
|
|
148
148
|
plugin = Plugin.from_jar(src_path)
|
|
149
|
-
rcs_path =
|
|
149
|
+
rcs_path = dst_dir.joinpath('RCS', f'{basename},v')
|
|
150
150
|
# Maybe do co -l before the parent's copy
|
|
151
151
|
if dst_path.exists() and rcs_path.is_file():
|
|
152
152
|
cmd = ['co', '-l', basename]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lockss_turtles-0.6.0.dev13 → lockss_turtles-0.6.0.dev15}/tests/unittest/lockss/turtles/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|