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/cli.py
CHANGED
|
@@ -29,76 +29,186 @@
|
|
|
29
29
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
30
30
|
|
|
31
31
|
"""
|
|
32
|
-
|
|
32
|
+
Command line tool for managing LOCKSS plugin sets and LOCKSS plugin registries.
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
+
# Remove in Python 3.11; see https://docs.python.org/3.11/library/exceptions.html#exception-groups
|
|
36
|
+
from exceptiongroup import ExceptionGroup
|
|
37
|
+
|
|
35
38
|
from getpass import getpass
|
|
36
39
|
from itertools import chain
|
|
37
40
|
from pathlib import Path
|
|
38
41
|
|
|
39
|
-
from exceptiongroup import ExceptionGroup
|
|
40
42
|
from lockss.pybasic.cliutil import BaseCli, StringCommand, COPYRIGHT_DESCRIPTION, LICENSE_DESCRIPTION, VERSION_DESCRIPTION
|
|
41
43
|
from lockss.pybasic.fileutil import file_lines, path
|
|
42
44
|
from lockss.pybasic.outpututil import OutputFormatOptions
|
|
43
45
|
from pydantic.v1 import BaseModel, Field, FilePath
|
|
44
|
-
from pydantic.v1.class_validators import validator
|
|
45
46
|
import tabulate
|
|
46
47
|
from typing import Optional
|
|
47
48
|
|
|
48
49
|
from . import __copyright__, __license__, __version__
|
|
49
|
-
from .app import
|
|
50
|
+
from .app import Turtles
|
|
51
|
+
from .plugin import PluginIdentifier
|
|
50
52
|
from .plugin_registry import PluginRegistryLayerIdentifier
|
|
51
53
|
from .util import file_or
|
|
52
54
|
|
|
53
55
|
|
|
54
56
|
class PluginBuildingOptions(BaseModel):
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
"""
|
|
58
|
+
Pydantic-Argparse (Pydantic v1) model for the ``--plugin-set``/``-s``,
|
|
59
|
+
``--plugin-set-catalog``/``-S``, ``--plugin-signing-credentials``/``-c``,
|
|
60
|
+
``--plugin-signing-password`` options.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
#: The ``--plugin-set``/``-s`` option.
|
|
64
|
+
plugin_set: Optional[list[FilePath]] = Field(aliases=['-s'],
|
|
65
|
+
title='Plugin Sets',
|
|
66
|
+
description=f'(plugin sets) add one or more plugin set definition files to the loaded plugin sets')
|
|
67
|
+
|
|
68
|
+
#: The ``--plugin-set-catalog``/``-S`` option.
|
|
69
|
+
plugin_set_catalog: Optional[list[FilePath]] = Field(aliases=['-S'],
|
|
70
|
+
title='Plugin Set Catalogs',
|
|
71
|
+
description=f'(plugin sets) add one or more plugin set catalogs to the loaded plugin set catalogs; if no plugin set catalogs or plugin sets are specified, load {file_or(Turtles.default_plugin_set_catalog_choices())}')
|
|
72
|
+
|
|
73
|
+
#: The ``--plugin-signing-credentials``/``-c`` option.
|
|
74
|
+
plugin_signing_credentials: Optional[FilePath] = Field(aliases=['-c'],
|
|
75
|
+
title='Plugin Signing Credentials',
|
|
76
|
+
description=f'(plugin signing credentials) load the plugin signing credentials from the given file, or if none, from {file_or(Turtles.default_plugin_signing_credentials_choices())}')
|
|
77
|
+
|
|
78
|
+
#: The ``--plugin-signing-password`` option.
|
|
79
|
+
plugin_signing_password: Optional[str] = Field(title='Plugin Signing Password',
|
|
80
|
+
description='(plugin signing credentials) set the plugin signing password, or if none, prompt interactively')
|
|
59
81
|
|
|
60
82
|
def get_plugin_sets(self) -> list[Path]:
|
|
83
|
+
"""
|
|
84
|
+
Returns the cumulative plugin set files.
|
|
85
|
+
|
|
86
|
+
:return: The cumulative plugin set files (possibly an empty list).
|
|
87
|
+
:rtype: list[Path]
|
|
88
|
+
"""
|
|
61
89
|
return [path(p) for p in self.plugin_set or []]
|
|
62
90
|
|
|
63
91
|
def get_plugin_set_catalogs(self) -> list[Path]:
|
|
92
|
+
"""
|
|
93
|
+
Returns the cumulative plugin set catalog files.
|
|
94
|
+
|
|
95
|
+
:return: The cumulative plugin set catalog files if any plugin set files
|
|
96
|
+
or plugin set catalog files are specified (possibly an empty
|
|
97
|
+
list), or the first default plugin set catalog file if no
|
|
98
|
+
plugin set files nor plugin set catalog files are specified.
|
|
99
|
+
:rtype: list[Path]
|
|
100
|
+
:raise FileNotFoundError: If no plugin set files nor plugin set catalog
|
|
101
|
+
files are specified, and none of the default
|
|
102
|
+
plugin set catalog file choices exist.
|
|
103
|
+
"""
|
|
64
104
|
if self.plugin_set or self.plugin_set_catalog:
|
|
65
105
|
return [path(p) for p in self.plugin_set_catalog or []]
|
|
66
|
-
if single :=
|
|
106
|
+
if single := Turtles.select_default_plugin_set_catalog():
|
|
67
107
|
return [single]
|
|
68
|
-
raise FileNotFoundError(file_or(
|
|
108
|
+
raise FileNotFoundError(file_or(Turtles.default_plugin_set_catalog_choices()))
|
|
69
109
|
|
|
70
110
|
def get_plugin_signing_credentials(self) -> Path:
|
|
111
|
+
"""
|
|
112
|
+
Returns the plugin signing credentials file.
|
|
113
|
+
|
|
114
|
+
:return: The plugin signing credentials file, or the first default
|
|
115
|
+
plugin signing credentials file if not specified.
|
|
116
|
+
:rtype: Path
|
|
117
|
+
:raise FileNotFoundError: If no plugin signing credentials file is
|
|
118
|
+
specified, and none of the default
|
|
119
|
+
plugin signing credentials file choices exist.
|
|
120
|
+
"""
|
|
71
121
|
if self.plugin_signing_credentials:
|
|
72
122
|
return path(self.plugin_signing_credentials)
|
|
73
|
-
if ret :=
|
|
123
|
+
if ret := Turtles.select_default_plugin_signing_credentials():
|
|
74
124
|
return ret
|
|
75
|
-
raise FileNotFoundError(file_or(
|
|
125
|
+
raise FileNotFoundError(file_or(Turtles.default_plugin_signing_credentials_choices()))
|
|
76
126
|
|
|
77
127
|
|
|
78
128
|
class PluginDeploymentOptions(BaseModel):
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
129
|
+
"""
|
|
130
|
+
Pydantic-Argparse (Pydantic v1) model for the ``--plugin-registry``/``-r``,
|
|
131
|
+
``--plugin-registry-catalog``/``-R``, ``--plugin-registry-layer``/``-l``,
|
|
132
|
+
``--plugin-registry-layers``/``-L``, ``--testing``/``-t``,
|
|
133
|
+
``--production``/``-P`` options.
|
|
134
|
+
"""
|
|
85
135
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
136
|
+
#: The ``--plugin-registry``/``-r`` option.
|
|
137
|
+
plugin_registry: Optional[list[FilePath]] = Field(aliases=['-r'],
|
|
138
|
+
title='Plugin Registries',
|
|
139
|
+
description=f'(plugin registry) add one or more plugin registries to the loaded plugin registries')
|
|
140
|
+
|
|
141
|
+
#: The ``--plugin-registry-catalog``/``-R`` option.
|
|
142
|
+
plugin_registry_catalog: Optional[list[FilePath]] = Field(aliases=['-R'],
|
|
143
|
+
title='Plugin Registry Catalogs',
|
|
144
|
+
description=f'(plugin registry) add one or more plugin registry catalogs to the loaded plugin registry catalogs; if no plugin registry catalogs or plugin registries are specified, load {file_or(Turtles.default_plugin_registry_catalog_choices())}')
|
|
145
|
+
|
|
146
|
+
#: The ``--plugin-registry-layer``/``-l`` option.
|
|
147
|
+
plugin_registry_layer: Optional[list[str]] = Field(aliases=['-l'],
|
|
148
|
+
title='Plugin Registry Layer Identifiers',
|
|
149
|
+
description='(plugin registry layers) add one or more plugin registry layers to the set of plugin registry layers to process')
|
|
150
|
+
|
|
151
|
+
#: The ``--plugin-registry-layers``/``-L`` option.
|
|
152
|
+
plugin_registry_layers: Optional[list[FilePath]] = Field(aliases=['-L'],
|
|
153
|
+
title='Files of Plugin Registry Layer Identifiers',
|
|
154
|
+
description='(plugin registry layers) add the plugin registry layers listed in one or more files to the set of plugin registry layers to process')
|
|
155
|
+
|
|
156
|
+
#: The ``--testing``/``-t`` option.
|
|
157
|
+
testing: Optional[bool] = Field(False,
|
|
158
|
+
aliases=['-t'],
|
|
159
|
+
title='Testing Layer',
|
|
160
|
+
description='(plugin registry layers) synonym for --plugin-registry-layer testing (i.e. add "testing" to the list of plugin registry layers to process)')
|
|
161
|
+
|
|
162
|
+
#: The ``--production``/``-P`` option.
|
|
163
|
+
production: Optional[bool] = Field(False,
|
|
164
|
+
aliases=['-p'],
|
|
165
|
+
title='Production Layer',
|
|
166
|
+
description='(plugin registry layers) synonym for --plugin-registry-layer production (i.e. add "production" to the list of plugin registry layers to process)')
|
|
89
167
|
|
|
90
168
|
def get_plugin_registries(self) -> list[Path]:
|
|
169
|
+
"""
|
|
170
|
+
Returns the cumulative plugin registry files.
|
|
171
|
+
|
|
172
|
+
:return: The cumulative plugin registry files (possibly an empty list).
|
|
173
|
+
:rtype: list[Path]
|
|
174
|
+
"""
|
|
91
175
|
return [path(p) for p in self.plugin_registry or []]
|
|
92
176
|
|
|
93
177
|
def get_plugin_registry_catalogs(self) -> list[Path]:
|
|
178
|
+
"""
|
|
179
|
+
Returns the cumulative plugin registry catalog files.
|
|
180
|
+
|
|
181
|
+
:return: The cumulative plugin registry catalog files if any plugin set
|
|
182
|
+
files or plugin registry catalog files are specified (possibly
|
|
183
|
+
an empty list), or the first default plugin registry catalog
|
|
184
|
+
file if no plugin registry files nor plugin registry catalog
|
|
185
|
+
files are specified.
|
|
186
|
+
:rtype: list[Path]
|
|
187
|
+
:raise FileNotFoundError: If no plugin registry files nor plugin
|
|
188
|
+
registry catalog files are specified, and none
|
|
189
|
+
of the default plugin registry catalog file
|
|
190
|
+
choices exist.
|
|
191
|
+
"""
|
|
94
192
|
if self.plugin_registry or self.plugin_registry_catalog:
|
|
95
193
|
return [path(p) for p in self.plugin_registry_catalog or []]
|
|
96
|
-
if single :=
|
|
194
|
+
if single := Turtles.select_default_plugin_registry_catalog():
|
|
97
195
|
return [single]
|
|
98
|
-
raise FileNotFoundError(file_or(
|
|
196
|
+
raise FileNotFoundError(file_or(Turtles.default_plugin_set_catalog_choices()))
|
|
99
197
|
|
|
100
198
|
def get_plugin_registry_layers(self) -> list[PluginRegistryLayerIdentifier]:
|
|
101
|
-
|
|
199
|
+
"""
|
|
200
|
+
Returns the cumulative list of plugin registry layer identifiers, from
|
|
201
|
+
``plugin_registry_layer`` and the identifiers in
|
|
202
|
+
``plugin_registry_layers`` files.
|
|
203
|
+
|
|
204
|
+
:return: The cumulative list of plugin registry layer identifiers, from
|
|
205
|
+
``plugin_registry_layer`` and the identifiers in
|
|
206
|
+
``plugin_registry_layers`` files.
|
|
207
|
+
:rtype: list[PluginRegistryLayerIdentifier]
|
|
208
|
+
:raise ValueError: If the list of plugin registry layer identifiers is
|
|
209
|
+
empty.
|
|
210
|
+
"""
|
|
211
|
+
ret = [*(self.plugin_registry_layer or []), *chain.from_iterable(file_lines(path(file_path)) for file_path in self.plugin_registry_layers or [])]
|
|
102
212
|
for layer in reversed(['testing', 'production']):
|
|
103
213
|
if getattr(self, layer, False) and layer not in ret:
|
|
104
214
|
ret.insert(0, layer)
|
|
@@ -109,17 +219,33 @@ class PluginDeploymentOptions(BaseModel):
|
|
|
109
219
|
|
|
110
220
|
class PluginIdentifierOptions(BaseModel):
|
|
111
221
|
"""
|
|
112
|
-
|
|
222
|
+
Pydantic-Argparse (Pydantic v1) models for the
|
|
223
|
+
``--plugin-identifier``/``-i``, ``--plugin-identifiers``/``-I`` options.
|
|
113
224
|
"""
|
|
114
|
-
plugin_identifier: Optional[list[str]] = Field(aliases=['-i'], description='(plugin identifiers) add one or more plugin identifiers to the set of plugin identifiers to process')
|
|
115
|
-
plugin_identifiers: Optional[list[FilePath]] = Field(aliases=['-I'], description='(plugin identifiers) add the plugin identifiers listed in one or more files to the set of plugin identifiers to process')
|
|
116
|
-
|
|
117
|
-
@validator('plugin_identifiers', each_item=True, pre=True)
|
|
118
|
-
def _expand_each_plugin_identifiers_path(cls, v: Path) -> Path:
|
|
119
|
-
return path(v)
|
|
120
225
|
|
|
121
|
-
|
|
122
|
-
|
|
226
|
+
#: The ``--plugin-identifier``/``-i`` option.
|
|
227
|
+
plugin_identifier: Optional[list[str]] = Field(aliases=['-i'],
|
|
228
|
+
title='Plugin Identifiers',
|
|
229
|
+
description='(plugin identifiers) add one or more plugin identifiers to the set of plugin identifiers to process')
|
|
230
|
+
|
|
231
|
+
#: The ``--plugin-identifiers``/``-I`` option.
|
|
232
|
+
plugin_identifiers: Optional[list[FilePath]] = Field(aliases=['-I'],
|
|
233
|
+
title='Files of Plugin Identifiers',
|
|
234
|
+
description='(plugin identifiers) add the plugin identifiers listed in one or more files to the set of plugin identifiers to process')
|
|
235
|
+
|
|
236
|
+
def get_plugin_identifiers(self) -> list[PluginIdentifier]:
|
|
237
|
+
"""
|
|
238
|
+
Returns the cumulative list of plugin identifiers, from
|
|
239
|
+
``plugin_identifier`` and the identifiers in ``plugin_identifiers``
|
|
240
|
+
files.
|
|
241
|
+
|
|
242
|
+
:return: The cumulative list of plugin identifiers, from
|
|
243
|
+
``plugin_identifier`` and the identifiers in
|
|
244
|
+
``plugin_identifiers`` files.
|
|
245
|
+
:rtype: list[PluginIdentifier]
|
|
246
|
+
:raise ValueError: If the list of plugin identifiers is empty.
|
|
247
|
+
"""
|
|
248
|
+
ret = [*(self.plugin_identifier or []), *chain.from_iterable(file_lines(path(file_path)) for file_path in self.plugin_identifiers or [])]
|
|
123
249
|
if ret:
|
|
124
250
|
return ret
|
|
125
251
|
raise ValueError('Empty list of plugin identifiers')
|
|
@@ -127,53 +253,116 @@ class PluginIdentifierOptions(BaseModel):
|
|
|
127
253
|
|
|
128
254
|
class PluginJarOptions(BaseModel):
|
|
129
255
|
"""
|
|
130
|
-
|
|
256
|
+
Pydantic-Argparse (Pydantic v1) model for the ``--plugin-jar``/``-j``,
|
|
257
|
+
``--plugin-jars``/``-J`` options.
|
|
131
258
|
"""
|
|
132
|
-
plugin_jar: Optional[list[FilePath]] = Field(aliases=['-j'], description='(plugin JARs) add one or more plugin JARs to the set of plugin JARs to process')
|
|
133
|
-
plugin_jars: Optional[list[FilePath]] = Field(aliases=['-J'], description='(plugin JARs) add the plugin JARs listed in one or more files to the set of plugin JARs to process')
|
|
134
259
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
260
|
+
#: The ``--plugin-jar``/``-j`` option.
|
|
261
|
+
plugin_jar: Optional[list[FilePath]] = Field(aliases=['-j'],
|
|
262
|
+
title='Plugin JARs',
|
|
263
|
+
description='(plugin JARs) add one or more plugin JARs to the set of plugin JARs to process')
|
|
264
|
+
|
|
265
|
+
#: The ``--plugin-jars``/``-J`` option.
|
|
266
|
+
plugin_jars: Optional[list[FilePath]] = Field(aliases=['-J'],
|
|
267
|
+
title='Files of Plugin JARs',
|
|
268
|
+
description='(plugin JARs) add the plugin JARs listed in one or more files to the set of plugin JARs to process')
|
|
269
|
+
|
|
270
|
+
def get_plugin_jars(self) -> list[Path]:
|
|
271
|
+
"""
|
|
272
|
+
Returns the cumulative list of plugin JARs, from ``plugin_jar`` and the
|
|
273
|
+
plugin JARs in ``plugin_jars``
|
|
274
|
+
files.
|
|
275
|
+
|
|
276
|
+
:return: The cumulative list of plugin JARs, from ``plugin_jar`` and the
|
|
277
|
+
plugin JARs in ``plugin_jars`` files.
|
|
278
|
+
:rtype: list[Path]
|
|
279
|
+
:raise ValueError: If the list of plugin JARs is empty.
|
|
280
|
+
"""
|
|
281
|
+
ret = [*(self.plugin_jar or []), *chain.from_iterable(file_lines(path(file_path)) for file_path in self.plugin_jars or [])]
|
|
141
282
|
if len(ret):
|
|
142
283
|
return ret
|
|
143
284
|
raise ValueError('Empty list of plugin JARs')
|
|
144
285
|
|
|
145
286
|
|
|
146
287
|
class NonInteractiveOptions(BaseModel):
|
|
288
|
+
"""
|
|
289
|
+
Pydantic-Argparse (Pydantic v1) model for the ``--non-interactive`` option.
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
#: The ``--non-interactive`` option.
|
|
147
293
|
non_interactive: Optional[bool] = Field(False,
|
|
294
|
+
title='Non-Interactive',
|
|
148
295
|
description='(plugin signing credentials) disallow interactive prompts')
|
|
149
296
|
|
|
150
297
|
|
|
151
298
|
class TurtlesCommand(BaseModel):
|
|
299
|
+
"""
|
|
300
|
+
Pydantic-Argparse (Pydantic v1) model for the ``turtles`` command.
|
|
301
|
+
"""
|
|
152
302
|
|
|
153
|
-
class BuildPluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginBuildingOptions, PluginIdentifierOptions):
|
|
154
|
-
|
|
155
|
-
|
|
303
|
+
class BuildPluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginBuildingOptions, PluginIdentifierOptions):
|
|
304
|
+
"""
|
|
305
|
+
Pydantic-Argparse (Pydantic v1) model for the ``build-plugin`` command.
|
|
306
|
+
"""
|
|
307
|
+
pass
|
|
308
|
+
|
|
309
|
+
class DeployPluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginDeploymentOptions, PluginJarOptions):
|
|
310
|
+
"""
|
|
311
|
+
Pydantic-Argparse (Pydantic v1) model for the ``deploy-plugin`` command.
|
|
312
|
+
"""
|
|
313
|
+
pass
|
|
314
|
+
|
|
315
|
+
class ReleasePluginCommand(OutputFormatOptions, NonInteractiveOptions, PluginDeploymentOptions, PluginBuildingOptions, PluginIdentifierOptions):
|
|
316
|
+
"""
|
|
317
|
+
Pydantic-Argparse (Pydantic v1) model for the ``release-plugin``
|
|
318
|
+
command.
|
|
319
|
+
"""
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
#: The ``bp`` synonym for the ``build-plugin`` command.
|
|
323
|
+
bp: Optional[BuildPluginCommand] = Field(description='synonym for: build-plugin')
|
|
156
324
|
|
|
157
|
-
|
|
325
|
+
#: The ``build-plugin`` command.
|
|
326
|
+
build_plugin: Optional[BuildPluginCommand] = Field(alias='build-plugin',
|
|
327
|
+
description='build plugins')
|
|
158
328
|
|
|
159
|
-
|
|
160
|
-
build_plugin: Optional[BuildPluginCommand] = Field(description='build plugins', alias='build-plugin')
|
|
329
|
+
#: The ``copyright`` command.
|
|
161
330
|
copyright: Optional[StringCommand.type(__copyright__)] = Field(description=COPYRIGHT_DESCRIPTION)
|
|
162
|
-
|
|
331
|
+
|
|
332
|
+
#: The ``deploy-plugin`` command.
|
|
333
|
+
deploy_plugin: Optional[DeployPluginCommand] = Field(alias='deploy-plugin',
|
|
334
|
+
description='deploy plugins')
|
|
335
|
+
|
|
336
|
+
#: The ``dp`` synonym for the ``deploy-plugin`` command.
|
|
163
337
|
dp: Optional[DeployPluginCommand] = Field(description='synonym for: deploy-plugin')
|
|
338
|
+
|
|
339
|
+
#: The ``license`` command.
|
|
164
340
|
license: Optional[StringCommand.type(__license__)] = Field(description=LICENSE_DESCRIPTION)
|
|
165
|
-
|
|
341
|
+
|
|
342
|
+
#: The ``release-plugin`` command.
|
|
343
|
+
release_plugin: Optional[ReleasePluginCommand] = Field(alias='release-plugin',
|
|
344
|
+
description='release (build and deploy) plugins')
|
|
345
|
+
|
|
346
|
+
#: The ``rp`` synonym for the ``release-plugin`` command.
|
|
166
347
|
rp: Optional[ReleasePluginCommand] = Field(description='synonym for: release-plugin')
|
|
348
|
+
|
|
349
|
+
#: The ``version`` command.
|
|
167
350
|
version: Optional[StringCommand.type(__version__)] = Field(description=VERSION_DESCRIPTION)
|
|
168
351
|
|
|
169
352
|
|
|
170
353
|
class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
354
|
+
"""
|
|
355
|
+
Command line tool for Turtles.
|
|
356
|
+
"""
|
|
171
357
|
|
|
172
358
|
def __init__(self):
|
|
359
|
+
"""
|
|
360
|
+
Constructor.
|
|
361
|
+
"""
|
|
173
362
|
super().__init__(model=TurtlesCommand,
|
|
174
363
|
prog='turtles',
|
|
175
364
|
description='Tool for managing LOCKSS plugin sets and LOCKSS plugin registries')
|
|
176
|
-
self._app:
|
|
365
|
+
self._app: Turtles = Turtles()
|
|
177
366
|
|
|
178
367
|
# def _analyze_registry(self):
|
|
179
368
|
# # Prerequisites
|
|
@@ -233,19 +422,33 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
|
233
422
|
# if len(result) > 0:
|
|
234
423
|
# self._tabulate(title, result, headers)
|
|
235
424
|
|
|
236
|
-
def _bp(self,
|
|
237
|
-
|
|
425
|
+
def _bp(self,
|
|
426
|
+
command: TurtlesCommand.BuildPluginCommand) -> None:
|
|
427
|
+
"""
|
|
428
|
+
Implementation of the ``bp`` command.
|
|
429
|
+
|
|
430
|
+
:param command: The command object.
|
|
431
|
+
:type command: TurtlesCommand.BuildPluginCommand
|
|
432
|
+
"""
|
|
433
|
+
return self._build_plugin(command)
|
|
434
|
+
|
|
435
|
+
def _build_plugin(self,
|
|
436
|
+
command: TurtlesCommand.BuildPluginCommand) -> None:
|
|
437
|
+
"""
|
|
438
|
+
Implementation of the ``build-plugin`` command.
|
|
238
439
|
|
|
239
|
-
|
|
440
|
+
:param command: The command object.
|
|
441
|
+
:type command: TurtlesCommand.BuildPluginCommand
|
|
442
|
+
"""
|
|
240
443
|
errs = []
|
|
241
|
-
for psc in
|
|
444
|
+
for psc in command.get_plugin_set_catalogs():
|
|
242
445
|
try:
|
|
243
446
|
self._app.load_plugin_set_catalogs(psc)
|
|
244
447
|
except ValueError as ve:
|
|
245
448
|
errs.append(ve)
|
|
246
449
|
except ExceptionGroup as eg:
|
|
247
450
|
errs.extend(eg.exceptions)
|
|
248
|
-
for ps in
|
|
451
|
+
for ps in command.get_plugin_sets():
|
|
249
452
|
try:
|
|
250
453
|
self._app.load_plugin_sets(ps)
|
|
251
454
|
except ValueError as ve:
|
|
@@ -253,35 +456,49 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
|
253
456
|
except ExceptionGroup as eg:
|
|
254
457
|
errs.extend(eg.exceptions)
|
|
255
458
|
try:
|
|
256
|
-
self._app.load_plugin_signing_credentials(
|
|
459
|
+
self._app.load_plugin_signing_credentials(command.get_plugin_signing_credentials())
|
|
257
460
|
except ValueError as ve:
|
|
258
461
|
errs.append(ve)
|
|
259
462
|
except ExceptionGroup as eg:
|
|
260
463
|
errs.extend(eg.exceptions)
|
|
261
464
|
if errs:
|
|
262
465
|
raise ExceptionGroup(f'Errors while setting up the environment for building plugins', errs)
|
|
263
|
-
self.
|
|
466
|
+
self._obtain_plugin_signing_password(command, non_interactive=command.non_interactive)
|
|
264
467
|
# Action
|
|
265
468
|
# ... plugin_id -> (set_id, jar_path, plugin)
|
|
266
|
-
ret = self._app.build_plugin(
|
|
469
|
+
ret = self._app.build_plugin(command.get_plugin_identifiers())
|
|
267
470
|
# Output
|
|
268
471
|
print(tabulate.tabulate([[plugin_id, plugin.get_version(), set_id, jar_path] for plugin_id, (set_id, jar_path, plugin) in ret.items()],
|
|
269
472
|
headers=['Plugin identifier', 'Plugin version', 'Plugin set', 'Plugin JAR'],
|
|
270
|
-
tablefmt=
|
|
271
|
-
|
|
272
|
-
def _copyright(self,
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
473
|
+
tablefmt=command.output_format))
|
|
474
|
+
|
|
475
|
+
def _copyright(self,
|
|
476
|
+
command: StringCommand) -> None:
|
|
477
|
+
"""
|
|
478
|
+
Implementation of the ``copyright`` command.
|
|
479
|
+
|
|
480
|
+
:param command: The command object.
|
|
481
|
+
:type command: StringCommand
|
|
482
|
+
"""
|
|
483
|
+
self._do_string_command(command)
|
|
484
|
+
|
|
485
|
+
def _deploy_plugin(self,
|
|
486
|
+
command: TurtlesCommand.DeployPluginCommand) -> None:
|
|
487
|
+
"""
|
|
488
|
+
Implementation of the ``deploy_plugin`` command.
|
|
489
|
+
|
|
490
|
+
:param command: The command object.
|
|
491
|
+
:type command: TurtlesCommand.DeployPluginCommand
|
|
492
|
+
"""
|
|
276
493
|
errs = []
|
|
277
|
-
for prc in
|
|
494
|
+
for prc in command.get_plugin_registry_catalogs():
|
|
278
495
|
try:
|
|
279
496
|
self._app.load_plugin_registry_catalogs(prc)
|
|
280
497
|
except ValueError as ve:
|
|
281
498
|
errs.append(ve)
|
|
282
499
|
except ExceptionGroup as eg:
|
|
283
500
|
errs.extend(eg.exceptions)
|
|
284
|
-
for pr in
|
|
501
|
+
for pr in command.get_plugin_registries():
|
|
285
502
|
try:
|
|
286
503
|
self._app.load_plugin_registries(pr)
|
|
287
504
|
except ValueError as ve:
|
|
@@ -292,56 +509,94 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
|
292
509
|
raise ExceptionGroup(f'Errors while setting up the environment for deploying plugins', errs)
|
|
293
510
|
# Action
|
|
294
511
|
# ... (src_path, plugin_id) -> list of (registry_id, layer_id, dst_path, plugin)
|
|
295
|
-
ret = self._app.deploy_plugin(
|
|
296
|
-
|
|
297
|
-
interactive=not
|
|
512
|
+
ret = self._app.deploy_plugin(command.get_plugin_jars(),
|
|
513
|
+
command.get_plugin_registry_layers(),
|
|
514
|
+
interactive=not command.non_interactive)
|
|
298
515
|
# Output
|
|
299
516
|
print(tabulate.tabulate([[src_path, plugin_id, plugin.get_version(), registry_id, layer_id, dst_path] for (src_path, plugin_id), val in ret.items() for registry_id, layer_id, dst_path, plugin in val],
|
|
300
517
|
headers=['Plugin JAR', 'Plugin identifier', 'Plugin version', 'Plugin registry', 'Plugin registry layer', 'Deployed JAR'],
|
|
301
|
-
tablefmt=
|
|
302
|
-
|
|
303
|
-
def _do_string_command(self,
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
518
|
+
tablefmt=command.output_format))
|
|
519
|
+
|
|
520
|
+
def _do_string_command(self,
|
|
521
|
+
command: StringCommand) -> None:
|
|
522
|
+
"""
|
|
523
|
+
Implementation of string commands.
|
|
524
|
+
|
|
525
|
+
:param command: The command object.
|
|
526
|
+
:type command: StringCommand
|
|
527
|
+
"""
|
|
528
|
+
command()
|
|
529
|
+
|
|
530
|
+
def _dp(self,
|
|
531
|
+
command: TurtlesCommand.DeployPluginCommand) -> None:
|
|
532
|
+
"""
|
|
533
|
+
Implementation of the ``dp`` command.
|
|
534
|
+
|
|
535
|
+
:param command: The command object.
|
|
536
|
+
:type command: TurtlesCommand.DeployPluginCommand
|
|
537
|
+
"""
|
|
538
|
+
return self._deploy_plugin(command)
|
|
539
|
+
|
|
540
|
+
def _license(self,
|
|
541
|
+
command: StringCommand) -> None:
|
|
542
|
+
"""
|
|
543
|
+
Implementation of the ``license`` command.
|
|
544
|
+
|
|
545
|
+
:param command: The command object.
|
|
546
|
+
:type command: StringCommand
|
|
547
|
+
"""
|
|
548
|
+
self._do_string_command(command)
|
|
549
|
+
|
|
550
|
+
def _obtain_plugin_signing_password(self,
|
|
551
|
+
plugin_building_options: PluginBuildingOptions,
|
|
552
|
+
non_interactive: bool=False) -> None:
|
|
553
|
+
"""
|
|
554
|
+
Ensures the plugin signing password is specified.
|
|
555
|
+
|
|
556
|
+
:param plugin_building_options:
|
|
557
|
+
:type plugin_building_options: PluginBuildingOptions
|
|
558
|
+
:param non_interactive:
|
|
559
|
+
:type non_interactive: bool
|
|
560
|
+
"""
|
|
313
561
|
if plugin_building_options.plugin_signing_password:
|
|
314
562
|
_p = plugin_building_options.plugin_signing_password
|
|
315
563
|
elif not non_interactive:
|
|
316
564
|
_p = getpass('Plugin signing password: ')
|
|
317
565
|
else:
|
|
318
566
|
self._parser.error('no plugin signing password specified while in non-interactive mode')
|
|
319
|
-
self._app.
|
|
567
|
+
self._app.set_plugin_signing_password(lambda: _p)
|
|
320
568
|
|
|
321
|
-
def _release_plugin(self,
|
|
569
|
+
def _release_plugin(self,
|
|
570
|
+
command: TurtlesCommand.ReleasePluginCommand) -> None:
|
|
571
|
+
"""
|
|
572
|
+
Implementation of the ``release-plugin`` command.
|
|
573
|
+
|
|
574
|
+
:param command: The command object.
|
|
575
|
+
:type command: TurtlesCommand.ReleasePluginCommand
|
|
576
|
+
"""
|
|
322
577
|
errs = []
|
|
323
|
-
for psc in
|
|
578
|
+
for psc in command.get_plugin_set_catalogs():
|
|
324
579
|
try:
|
|
325
580
|
self._app.load_plugin_set_catalogs(psc)
|
|
326
581
|
except ValueError as ve:
|
|
327
582
|
errs.append(ve)
|
|
328
583
|
except ExceptionGroup as eg:
|
|
329
584
|
errs.extend(eg.exceptions)
|
|
330
|
-
for ps in
|
|
585
|
+
for ps in command.get_plugin_sets():
|
|
331
586
|
try:
|
|
332
587
|
self._app.load_plugin_sets(ps)
|
|
333
588
|
except ValueError as ve:
|
|
334
589
|
errs.append(ve)
|
|
335
590
|
except ExceptionGroup as eg:
|
|
336
591
|
errs.extend(eg.exceptions)
|
|
337
|
-
for prc in
|
|
592
|
+
for prc in command.get_plugin_registry_catalogs():
|
|
338
593
|
try:
|
|
339
594
|
self._app.load_plugin_registry_catalogs(prc)
|
|
340
595
|
except ValueError as ve:
|
|
341
596
|
errs.append(ve)
|
|
342
597
|
except ExceptionGroup as eg:
|
|
343
598
|
errs.extend(eg.exceptions)
|
|
344
|
-
for pr in
|
|
599
|
+
for pr in command.get_plugin_registries():
|
|
345
600
|
try:
|
|
346
601
|
self._app.load_plugin_registries(pr)
|
|
347
602
|
except ValueError as ve:
|
|
@@ -349,34 +604,50 @@ class TurtlesCli(BaseCli[TurtlesCommand]):
|
|
|
349
604
|
except ExceptionGroup as eg:
|
|
350
605
|
errs.extend(eg.exceptions)
|
|
351
606
|
try:
|
|
352
|
-
self._app.load_plugin_signing_credentials(
|
|
607
|
+
self._app.load_plugin_signing_credentials(command.get_plugin_signing_credentials())
|
|
353
608
|
except ValueError as ve:
|
|
354
609
|
errs.append(ve)
|
|
355
610
|
except ExceptionGroup as eg:
|
|
356
611
|
errs.extend(eg.exceptions)
|
|
357
612
|
if errs:
|
|
358
613
|
raise ExceptionGroup(f'Errors while setting up the environment for deploying plugins', errs)
|
|
359
|
-
self.
|
|
614
|
+
self._obtain_plugin_signing_password(command, non_interactive=command.non_interactive)
|
|
360
615
|
# Action
|
|
361
616
|
# ... plugin_id -> list of (registry_id, layer_id, dst_path, plugin)
|
|
362
|
-
ret = self._app.release_plugin(
|
|
363
|
-
|
|
364
|
-
interactive=not
|
|
617
|
+
ret = self._app.release_plugin(command.get_plugin_identifiers(),
|
|
618
|
+
command.get_plugin_registry_layers(),
|
|
619
|
+
interactive=not command.non_interactive)
|
|
365
620
|
# Output
|
|
366
621
|
print(tabulate.tabulate([[plugin_id, plugin.get_version(), registry_id, layer_id, dst_path] for plugin_id, val in ret.items() for registry_id, layer_id, dst_path, plugin in val],
|
|
367
622
|
headers=['Plugin identifier', 'Plugin version', 'Plugin registry', 'Plugin registry layer', 'Deployed JAR'],
|
|
368
|
-
tablefmt=
|
|
623
|
+
tablefmt=command.output_format))
|
|
624
|
+
|
|
625
|
+
def _rp(self, command: TurtlesCommand.ReleasePluginCommand) -> None:
|
|
626
|
+
"""
|
|
627
|
+
Implementation of the ``rp`` command.
|
|
628
|
+
|
|
629
|
+
:param command: The command object.
|
|
630
|
+
:type command: TurtlesCommand.ReleasePluginCommand
|
|
631
|
+
"""
|
|
632
|
+
self._release_plugin(command)
|
|
369
633
|
|
|
370
|
-
def
|
|
371
|
-
|
|
634
|
+
def _version(self, command: StringCommand) -> None:
|
|
635
|
+
"""
|
|
636
|
+
Implementation of the ``version`` command.
|
|
372
637
|
|
|
373
|
-
|
|
374
|
-
|
|
638
|
+
:param command: The command object.
|
|
639
|
+
:type command: StringCommand
|
|
640
|
+
"""
|
|
641
|
+
self._do_string_command(command)
|
|
375
642
|
|
|
376
643
|
|
|
377
|
-
def main():
|
|
644
|
+
def main() -> None:
|
|
645
|
+
"""
|
|
646
|
+
Main entry point of the module.
|
|
647
|
+
"""
|
|
378
648
|
TurtlesCli().run()
|
|
379
649
|
|
|
380
650
|
|
|
651
|
+
# Main entry point of the module.
|
|
381
652
|
if __name__ == '__main__':
|
|
382
653
|
main()
|