lockss-turtles 0.3.0.dev2__py3-none-any.whl → 0.3.0.dev4__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.
- CHANGELOG.rst +77 -0
- lockss/turtles/__init__.py +1 -1
- lockss/turtles/app.py +18 -16
- lockss/turtles/cli.py +12 -10
- lockss/turtles/plugin_registry.py +8 -9
- lockss/turtles/resources/plugin-registry-schema.json +7 -0
- lockss/turtles/resources/plugin-set-schema.json +23 -19
- lockss/turtles/resources/{plugin-signing-schema.json → plugin-signing-credentials-schema.json} +4 -4
- {lockss_turtles-0.3.0.dev2.dist-info → lockss_turtles-0.3.0.dev4.dist-info}/LICENSE +1 -1
- lockss_turtles-0.3.0.dev4.dist-info/METADATA +802 -0
- lockss_turtles-0.3.0.dev4.dist-info/RECORD +20 -0
- {lockss_turtles-0.3.0.dev2.dist-info → lockss_turtles-0.3.0.dev4.dist-info}/WHEEL +1 -1
- lockss/turtles/turtles.py +0 -1109
- lockss_turtles-0.3.0.dev2.dist-info/METADATA +0 -607
- lockss_turtles-0.3.0.dev2.dist-info/RECORD +0 -20
- {lockss_turtles-0.3.0.dev2.dist-info → lockss_turtles-0.3.0.dev4.dist-info}/entry_points.txt +0 -0
lockss/turtles/turtles.py
DELETED
|
@@ -1,1109 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
__copyright__ = '''\
|
|
4
|
-
Copyright (c) 2000-2022, Board of Trustees of Leland Stanford Jr. University
|
|
5
|
-
'''
|
|
6
|
-
|
|
7
|
-
__license__ = '''\
|
|
8
|
-
Redistribution and use in source and binary forms, with or without
|
|
9
|
-
modification, are permitted provided that the following conditions are met:
|
|
10
|
-
|
|
11
|
-
1. Redistributions of source code must retain the above copyright notice,
|
|
12
|
-
this list of conditions and the following disclaimer.
|
|
13
|
-
|
|
14
|
-
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
15
|
-
this list of conditions and the following disclaimer in the documentation
|
|
16
|
-
and/or other materials provided with the distribution.
|
|
17
|
-
|
|
18
|
-
3. Neither the name of the copyright holder nor the names of its contributors
|
|
19
|
-
may be used to endorse or promote products derived from this software without
|
|
20
|
-
specific prior written permission.
|
|
21
|
-
|
|
22
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
23
|
-
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
24
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
25
|
-
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
26
|
-
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
27
|
-
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
28
|
-
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
29
|
-
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
30
|
-
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
31
|
-
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
32
|
-
POSSIBILITY OF SUCH DAMAGE.
|
|
33
|
-
'''
|
|
34
|
-
|
|
35
|
-
__version__ = '0.3.0-dev'
|
|
36
|
-
|
|
37
|
-
import argparse
|
|
38
|
-
import getpass
|
|
39
|
-
import java_manifest
|
|
40
|
-
import os
|
|
41
|
-
from pathlib import Path, PurePath
|
|
42
|
-
import shlex
|
|
43
|
-
import subprocess
|
|
44
|
-
import sys
|
|
45
|
-
import tabulate
|
|
46
|
-
import xdg
|
|
47
|
-
import xml.etree.ElementTree as ET
|
|
48
|
-
import yaml
|
|
49
|
-
import zipfile
|
|
50
|
-
|
|
51
|
-
PROG = 'turtles.sh'
|
|
52
|
-
|
|
53
|
-
def _file_lines(path):
|
|
54
|
-
f = None
|
|
55
|
-
try:
|
|
56
|
-
f = open(_path(path), 'r') if path != '-' else sys.stdin
|
|
57
|
-
return [line for line in [line.partition('#')[0].strip() for line in f] if len(line) > 0]
|
|
58
|
-
finally:
|
|
59
|
-
if f is not None and path != '-':
|
|
60
|
-
f.close()
|
|
61
|
-
|
|
62
|
-
def _path(purepath_or_string):
|
|
63
|
-
if not issubclass(type(purepath_or_string), PurePath):
|
|
64
|
-
purepath_or_string=Path(purepath_or_string)
|
|
65
|
-
return purepath_or_string.expanduser().resolve()
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
class Plugin(object):
|
|
69
|
-
|
|
70
|
-
@staticmethod
|
|
71
|
-
def from_jar(jar_path):
|
|
72
|
-
jar_path = _path(jar_path) # in case it's a string
|
|
73
|
-
plugin_id = Plugin.id_from_jar(jar_path)
|
|
74
|
-
plugin_fstr = str(Plugin.id_to_file(plugin_id))
|
|
75
|
-
with zipfile.ZipFile(jar_path, 'r') as zip_file:
|
|
76
|
-
with zip_file.open(plugin_fstr, 'r') as plugin_file:
|
|
77
|
-
return Plugin(plugin_file, plugin_fstr)
|
|
78
|
-
|
|
79
|
-
@staticmethod
|
|
80
|
-
def from_path(path):
|
|
81
|
-
path = _path(path) # in case it's a string
|
|
82
|
-
with open(path, 'r') as input_file:
|
|
83
|
-
return Plugin(input_file, path)
|
|
84
|
-
|
|
85
|
-
@staticmethod
|
|
86
|
-
def file_to_id(plugin_fstr):
|
|
87
|
-
return plugin_fstr.replace('/', '.')[:-4] # 4 is len('.xml')
|
|
88
|
-
|
|
89
|
-
@staticmethod
|
|
90
|
-
def id_from_jar(jar_path):
|
|
91
|
-
jar_path = _path(jar_path) # in case it's a string
|
|
92
|
-
manifest = java_manifest.from_jar(jar_path)
|
|
93
|
-
for entry in manifest:
|
|
94
|
-
if entry.get('Lockss-Plugin') == 'true':
|
|
95
|
-
name = entry.get('Name')
|
|
96
|
-
if name is None:
|
|
97
|
-
raise Exception(f'{jar_path!s}: Lockss-Plugin entry in META-INF/MANIFEST.MF has no Name value')
|
|
98
|
-
return Plugin.file_to_id(name)
|
|
99
|
-
else:
|
|
100
|
-
raise Exception(f'{jar_path!s}: no Lockss-Plugin entry in META-INF/MANIFEST.MF')
|
|
101
|
-
|
|
102
|
-
@staticmethod
|
|
103
|
-
def id_to_dir(plugin_id):
|
|
104
|
-
return Plugin.id_to_file(plugin_id).parent
|
|
105
|
-
|
|
106
|
-
@staticmethod
|
|
107
|
-
def id_to_file(plugin_id):
|
|
108
|
-
return Path(f'{plugin_id.replace(".", "/")}.xml')
|
|
109
|
-
|
|
110
|
-
def __init__(self, plugin_file, plugin_path):
|
|
111
|
-
super().__init__()
|
|
112
|
-
self._path = plugin_path
|
|
113
|
-
self._parsed = ET.parse(plugin_file).getroot()
|
|
114
|
-
tag = self._parsed.tag
|
|
115
|
-
if tag != 'map':
|
|
116
|
-
raise RuntimeError(f'{plugin_path!s}: invalid root element: {tag}')
|
|
117
|
-
|
|
118
|
-
def name(self):
|
|
119
|
-
return self._only_one('plugin_name')
|
|
120
|
-
|
|
121
|
-
def identifier(self):
|
|
122
|
-
return self._only_one('plugin_identifier')
|
|
123
|
-
|
|
124
|
-
def parent_identifier(self):
|
|
125
|
-
return self._only_one('plugin_parent')
|
|
126
|
-
|
|
127
|
-
def parent_version(self):
|
|
128
|
-
return self._only_one('plugin_parent_version', int)
|
|
129
|
-
|
|
130
|
-
def version(self):
|
|
131
|
-
return self._only_one('plugin_version', int)
|
|
132
|
-
|
|
133
|
-
def _only_one(self, key, result=str):
|
|
134
|
-
lst = [x[1].text for x in self._parsed.findall('entry') if x[0].tag == 'string' and x[0].text == key]
|
|
135
|
-
if lst is None or len(lst) < 1:
|
|
136
|
-
return None
|
|
137
|
-
if len(lst) > 1:
|
|
138
|
-
raise ValueError(f'plugin declares {len(lst)} entries for {key}')
|
|
139
|
-
return result(lst[0])
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
class PluginRegistry(object):
|
|
143
|
-
|
|
144
|
-
KIND = 'PluginRegistry'
|
|
145
|
-
|
|
146
|
-
@staticmethod
|
|
147
|
-
def from_path(path):
|
|
148
|
-
path = _path(path)
|
|
149
|
-
with path.open('r') as f:
|
|
150
|
-
return [PluginRegistry.from_yaml(parsed, path) for parsed in yaml.safe_load_all(f)]
|
|
151
|
-
|
|
152
|
-
@staticmethod
|
|
153
|
-
def from_yaml(parsed, path):
|
|
154
|
-
kind = parsed.get('kind')
|
|
155
|
-
if kind is None:
|
|
156
|
-
raise RuntimeError(f'{path}: kind is not defined')
|
|
157
|
-
elif kind != PluginRegistry.KIND:
|
|
158
|
-
raise RuntimeError(f'{path}: not of kind {PluginRegistry.KIND}: {kind}')
|
|
159
|
-
layout = parsed.get('layout')
|
|
160
|
-
if layout is None:
|
|
161
|
-
raise RuntimeError(f'{path}: layout is not defined')
|
|
162
|
-
typ = layout.get('type')
|
|
163
|
-
if typ is None:
|
|
164
|
-
raise RuntimeError(f'{path}: layout type is not defined')
|
|
165
|
-
elif typ == DirectoryPluginRegistry.LAYOUT:
|
|
166
|
-
return DirectoryPluginRegistry(parsed)
|
|
167
|
-
elif typ == RcsPluginRegistry.LAYOUT:
|
|
168
|
-
return RcsPluginRegistry(parsed)
|
|
169
|
-
else:
|
|
170
|
-
raise RuntimeError(f'{path}: unknown layout type: {typ}')
|
|
171
|
-
|
|
172
|
-
def __init__(self, parsed):
|
|
173
|
-
super().__init__()
|
|
174
|
-
self._parsed = parsed
|
|
175
|
-
|
|
176
|
-
def get_layer(self, layerid):
|
|
177
|
-
for layer in self.get_layers():
|
|
178
|
-
if layer.id() == layerid:
|
|
179
|
-
return layer
|
|
180
|
-
return None
|
|
181
|
-
|
|
182
|
-
def get_layer_ids(self):
|
|
183
|
-
return [layer.id() for layer in self.get_layers()]
|
|
184
|
-
|
|
185
|
-
def get_layers(self):
|
|
186
|
-
return [self._make_layer(layer_elem) for layer_elem in self._parsed['layers']]
|
|
187
|
-
|
|
188
|
-
def has_plugin(self, plugid):
|
|
189
|
-
return plugid in self.plugin_identifiers()
|
|
190
|
-
|
|
191
|
-
def id(self):
|
|
192
|
-
return self._parsed['id']
|
|
193
|
-
|
|
194
|
-
def layout_type(self):
|
|
195
|
-
return self._parsed['layout']['type']
|
|
196
|
-
|
|
197
|
-
def layout_options(self):
|
|
198
|
-
return self._parsed['layout'].get('options', dict())
|
|
199
|
-
|
|
200
|
-
def name(self):
|
|
201
|
-
return self._parsed['name']
|
|
202
|
-
|
|
203
|
-
def plugin_identifiers(self):
|
|
204
|
-
return self._parsed['plugin-identifiers']
|
|
205
|
-
|
|
206
|
-
def _make_layer(self, parsed):
|
|
207
|
-
raise NotImplementedError('_make_layer')
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
class PluginRegistryLayer(object):
|
|
211
|
-
|
|
212
|
-
PRODUCTION = 'production'
|
|
213
|
-
TESTING = 'testing'
|
|
214
|
-
|
|
215
|
-
def __init__(self, plugin_registry, parsed):
|
|
216
|
-
super().__init__()
|
|
217
|
-
self._parsed = parsed
|
|
218
|
-
self._plugin_registry = plugin_registry
|
|
219
|
-
|
|
220
|
-
# Returns (dst_path, plugin)
|
|
221
|
-
def deploy_plugin(self, plugin_id, jar_path, interactive=False):
|
|
222
|
-
raise NotImplementedError('deploy_plugin')
|
|
223
|
-
|
|
224
|
-
def get_file_for(self, plugin_id):
|
|
225
|
-
raise NotImplementedError('get_file_for')
|
|
226
|
-
|
|
227
|
-
def get_jars(self):
|
|
228
|
-
raise NotImplementedError('get_jars')
|
|
229
|
-
|
|
230
|
-
def id(self):
|
|
231
|
-
return self._parsed['id']
|
|
232
|
-
|
|
233
|
-
def name(self):
|
|
234
|
-
return self._parsed['name']
|
|
235
|
-
|
|
236
|
-
def path(self):
|
|
237
|
-
return _path(self._parsed['path'])
|
|
238
|
-
|
|
239
|
-
def plugin_registry(self):
|
|
240
|
-
return self._plugin_registry
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
class DirectoryPluginRegistry(PluginRegistry):
|
|
244
|
-
|
|
245
|
-
LAYOUT = 'directory'
|
|
246
|
-
|
|
247
|
-
def __init__(self, parsed):
|
|
248
|
-
super().__init__(parsed)
|
|
249
|
-
|
|
250
|
-
def _make_layer(self, parsed):
|
|
251
|
-
return DirectoryPluginRegistryLayer(self, parsed)
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
class DirectoryPluginRegistryLayer(PluginRegistryLayer):
|
|
255
|
-
|
|
256
|
-
def __init__(self, plugin_registry, parsed):
|
|
257
|
-
super().__init__(plugin_registry, parsed)
|
|
258
|
-
|
|
259
|
-
def deploy_plugin(self, plugin_id, src_path, interactive=False):
|
|
260
|
-
src_path = _path(src_path) # in case it's a string
|
|
261
|
-
dst_path = self._get_dstpath(plugin_id)
|
|
262
|
-
if not self._proceed_copy(src_path, dst_path, interactive=interactive):
|
|
263
|
-
return None
|
|
264
|
-
self._copy_jar(src_path, dst_path, interactive=interactive)
|
|
265
|
-
return (dst_path, Plugin.from_jar(src_path))
|
|
266
|
-
|
|
267
|
-
def get_file_for(self, plugin_id):
|
|
268
|
-
jar_path = self._get_dstpath(plugin_id)
|
|
269
|
-
return jar_path if jar_path.is_file() else None
|
|
270
|
-
|
|
271
|
-
def get_jars(self):
|
|
272
|
-
return sorted(self.path().glob('*.jar'))
|
|
273
|
-
|
|
274
|
-
def _copy_jar(self, src_path, dst_path, interactive=False):
|
|
275
|
-
basename = dst_path.name
|
|
276
|
-
subprocess.run(['cp', str(src_path), str(dst_path)], check=True, cwd=self.path())
|
|
277
|
-
if subprocess.run('command -v selinuxenabled > /dev/null && selinuxenabled && command -v chcon > /dev/null',
|
|
278
|
-
shell=True).returncode == 0:
|
|
279
|
-
cmd = ['chcon', '-t', 'httpd_sys_content_t', basename]
|
|
280
|
-
subprocess.run(cmd, check=True, cwd=self.path())
|
|
281
|
-
|
|
282
|
-
def _get_dstpath(self, plugin_id):
|
|
283
|
-
return Path(self.path(), self._get_dstfile(plugin_id))
|
|
284
|
-
|
|
285
|
-
def _get_dstfile(self, plugin_id):
|
|
286
|
-
return f'{plugin_id}.jar'
|
|
287
|
-
|
|
288
|
-
def _proceed_copy(self, src_path, dst_path, interactive=False):
|
|
289
|
-
if not dst_path.exists():
|
|
290
|
-
if interactive:
|
|
291
|
-
i = input(
|
|
292
|
-
f'{dst_path} does not exist in {self.plugin_registry().id()}:{self.id()} ({self.name()}); create it (y/n)? [n] ').lower() or 'n'
|
|
293
|
-
if i != 'y':
|
|
294
|
-
return False
|
|
295
|
-
return True
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
class RcsPluginRegistry(DirectoryPluginRegistry):
|
|
299
|
-
|
|
300
|
-
LAYOUT = 'rcs'
|
|
301
|
-
|
|
302
|
-
FULL = 'full'
|
|
303
|
-
ABBREVIATED = 'abbreviated'
|
|
304
|
-
|
|
305
|
-
def __init__(self, parsed):
|
|
306
|
-
super().__init__(parsed)
|
|
307
|
-
|
|
308
|
-
def _make_layer(self, parsed):
|
|
309
|
-
return RcsPluginRegistryLayer(self, parsed)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
class RcsPluginRegistryLayer(DirectoryPluginRegistryLayer):
|
|
313
|
-
|
|
314
|
-
def __init__(self, plugin_registry, parsed):
|
|
315
|
-
super().__init__(plugin_registry, parsed)
|
|
316
|
-
|
|
317
|
-
def _copy_jar(self, src_path, dst_path, interactive=False):
|
|
318
|
-
basename = dst_path.name
|
|
319
|
-
plugin = Plugin.from_jar(src_path)
|
|
320
|
-
rcs_path = self.path().joinpath('RCS', f'{basename},v')
|
|
321
|
-
# Maybe do co -l before the parent's copy
|
|
322
|
-
if dst_path.exists() and rcs_path.is_file():
|
|
323
|
-
cmd = ['co', '-l', basename]
|
|
324
|
-
subprocess.run(cmd, check=True, cwd=self.path())
|
|
325
|
-
# Do the parent's copy
|
|
326
|
-
super()._copy_jar(src_path, dst_path)
|
|
327
|
-
# Do ci -u after the aprent's copy
|
|
328
|
-
cmd = ['ci', '-u', f'-mVersion {plugin.version()}']
|
|
329
|
-
if not rcs_path.is_file():
|
|
330
|
-
cmd.append(f'-t-{plugin.name()}')
|
|
331
|
-
cmd.append(basename)
|
|
332
|
-
subprocess.run(cmd, check=True, cwd=self.path())
|
|
333
|
-
|
|
334
|
-
def _get_dstfile(self, plugid):
|
|
335
|
-
conv = self.plugin_registry().layout_options().get('file-naming-convention')
|
|
336
|
-
if conv == RcsPluginRegistry.ABBREVIATED:
|
|
337
|
-
return f'{plugid.split(".")[-1]}.jar'
|
|
338
|
-
elif conv == RcsPluginRegistry.FULL or conv is None:
|
|
339
|
-
return super()._get_dstfile(plugid)
|
|
340
|
-
else:
|
|
341
|
-
raise RuntimeError(f'{self.plugin_registry().id()}: unknown file naming convention in layout options: {conv}')
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
class PluginSet(object):
|
|
345
|
-
|
|
346
|
-
KIND = 'PluginSet'
|
|
347
|
-
|
|
348
|
-
@staticmethod
|
|
349
|
-
def from_path(path):
|
|
350
|
-
path = _path(path)
|
|
351
|
-
with path.open('r') as f:
|
|
352
|
-
return [PluginSet.from_yaml(parsed, path) for parsed in yaml.safe_load_all(f)]
|
|
353
|
-
|
|
354
|
-
@staticmethod
|
|
355
|
-
def from_yaml(parsed, path):
|
|
356
|
-
kind = parsed.get('kind')
|
|
357
|
-
if kind is None:
|
|
358
|
-
raise RuntimeError(f'{path}: kind is not defined')
|
|
359
|
-
elif kind != PluginSet.KIND:
|
|
360
|
-
raise RuntimeError(f'{path}: not of kind {PluginSet.KIND}: {kind}')
|
|
361
|
-
builder = parsed.get('builder')
|
|
362
|
-
if builder is None:
|
|
363
|
-
raise RuntimeError(f'{path}: builder is not defined')
|
|
364
|
-
typ = builder.get('type')
|
|
365
|
-
if typ is None:
|
|
366
|
-
raise RuntimeError(f'{path}: builder type is not defined')
|
|
367
|
-
elif typ == AntPluginSet.TYPE:
|
|
368
|
-
return AntPluginSet(parsed, path)
|
|
369
|
-
elif typ == 'mvn':
|
|
370
|
-
return MavenPluginSet(parsed, path)
|
|
371
|
-
else:
|
|
372
|
-
raise RuntimeError(f'{path}: unknown builder type: {typ}')
|
|
373
|
-
|
|
374
|
-
def __init__(self, parsed):
|
|
375
|
-
super().__init__()
|
|
376
|
-
self._parsed = parsed
|
|
377
|
-
|
|
378
|
-
# Returns (jar_path, plugin)
|
|
379
|
-
def build_plugin(self, plugin_id, keystore_path, keystore_alias, keystore_password=None):
|
|
380
|
-
raise NotImplementedError('build_plugin')
|
|
381
|
-
|
|
382
|
-
def builder_type(self):
|
|
383
|
-
return self._parsed['builder']['type']
|
|
384
|
-
|
|
385
|
-
def builder_options(self):
|
|
386
|
-
return self._parsed['builder'].get('options', dict())
|
|
387
|
-
|
|
388
|
-
def has_plugin(self, plugid):
|
|
389
|
-
raise NotImplementedError('has_plugin')
|
|
390
|
-
|
|
391
|
-
def id(self):
|
|
392
|
-
return self._parsed['id']
|
|
393
|
-
|
|
394
|
-
def make_plugin(self, plugid):
|
|
395
|
-
raise NotImplementedError('make_plugin')
|
|
396
|
-
|
|
397
|
-
def name(self):
|
|
398
|
-
return self._parsed['name']
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
class AntPluginSet(PluginSet):
|
|
402
|
-
|
|
403
|
-
TYPE = 'ant'
|
|
404
|
-
|
|
405
|
-
def __init__(self, parsed, path):
|
|
406
|
-
super().__init__(parsed)
|
|
407
|
-
self._built = False
|
|
408
|
-
self._root = path.parent
|
|
409
|
-
|
|
410
|
-
# Returns (jar_path, plugin)
|
|
411
|
-
def build_plugin(self, plugin_id, keystore_path, keystore_alias, keystore_password=None):
|
|
412
|
-
# Prerequisites
|
|
413
|
-
if 'JAVA_HOME' not in os.environ:
|
|
414
|
-
raise RuntimeError('error: JAVA_HOME must be set in your environment')
|
|
415
|
-
# Big build (maybe)
|
|
416
|
-
self._big_build()
|
|
417
|
-
# Little build
|
|
418
|
-
return self._little_build(plugin_id, keystore_path, keystore_alias, keystore_password=keystore_password)
|
|
419
|
-
|
|
420
|
-
def has_plugin(self, plugin_id):
|
|
421
|
-
return self._plugin_path(plugin_id).is_file()
|
|
422
|
-
|
|
423
|
-
def main(self):
|
|
424
|
-
return self._parsed.get('main', 'plugins/src')
|
|
425
|
-
|
|
426
|
-
def main_path(self):
|
|
427
|
-
return self.root_path().joinpath(self.main())
|
|
428
|
-
|
|
429
|
-
def make_plugin(self, plugin_id):
|
|
430
|
-
return Plugin.from_path(self._plugin_path(plugin_id))
|
|
431
|
-
|
|
432
|
-
def root(self):
|
|
433
|
-
return self._root
|
|
434
|
-
|
|
435
|
-
def root_path(self):
|
|
436
|
-
return Path(self.root()).expanduser().resolve()
|
|
437
|
-
|
|
438
|
-
def test(self):
|
|
439
|
-
return self._parsed.get('test', 'plugins/test/src')
|
|
440
|
-
|
|
441
|
-
def test_path(self):
|
|
442
|
-
return self.root_path().joinpath(self.test())
|
|
443
|
-
|
|
444
|
-
def _big_build(self):
|
|
445
|
-
if not self._built:
|
|
446
|
-
# Do build
|
|
447
|
-
subprocess.run('ant load-plugins',
|
|
448
|
-
shell=True, cwd=self.root_path(), check=True, stdout=sys.stdout, stderr=sys.stderr)
|
|
449
|
-
self._built = True
|
|
450
|
-
|
|
451
|
-
# Returns (jar_path, plugin)
|
|
452
|
-
def _little_build(self, plugin_id, keystore_path, keystore_alias, keystore_password=None):
|
|
453
|
-
plugin = self.make_plugin(plugin_id)
|
|
454
|
-
# Get all directories for jarplugin -d
|
|
455
|
-
dirs = list()
|
|
456
|
-
cur_id = plugin_id
|
|
457
|
-
while cur_id is not None:
|
|
458
|
-
cur_dir = Plugin.id_to_dir(cur_id)
|
|
459
|
-
if cur_dir not in dirs:
|
|
460
|
-
dirs.append(cur_dir)
|
|
461
|
-
cur_id = self.make_plugin(cur_id).parent_identifier()
|
|
462
|
-
# Invoke jarplugin
|
|
463
|
-
jar_fstr = Plugin.id_to_file(plugin_id)
|
|
464
|
-
jar_path = self.root_path().joinpath('plugins/jars', f'{plugin_id}.jar')
|
|
465
|
-
jar_path.parent.mkdir(parents=True, exist_ok=True)
|
|
466
|
-
cmd = ['test/scripts/jarplugin',
|
|
467
|
-
'-j', str(jar_path),
|
|
468
|
-
'-p', str(jar_fstr)]
|
|
469
|
-
for dir in dirs:
|
|
470
|
-
cmd.extend(['-d', dir])
|
|
471
|
-
subprocess.run(cmd, cwd=self.root_path(), check=True, stdout=sys.stdout, stderr=sys.stderr)
|
|
472
|
-
# Invoke signplugin
|
|
473
|
-
cmd = ['test/scripts/signplugin',
|
|
474
|
-
'--jar', str(jar_path),
|
|
475
|
-
'--alias', keystore_alias,
|
|
476
|
-
'--keystore', str(keystore_path)]
|
|
477
|
-
if keystore_password is not None:
|
|
478
|
-
cmd.extend(['--password', keystore_password])
|
|
479
|
-
try:
|
|
480
|
-
subprocess.run(cmd, cwd=self.root_path(), check=True, stdout=sys.stdout, stderr=sys.stderr)
|
|
481
|
-
except subprocess.CalledProcessError as cpe:
|
|
482
|
-
raise self._sanitize(cpe)
|
|
483
|
-
if not jar_path.is_file():
|
|
484
|
-
raise FileNotFoundError(str(jar_path))
|
|
485
|
-
return (jar_path, plugin)
|
|
486
|
-
|
|
487
|
-
def _plugin_path(self, plugin_id):
|
|
488
|
-
return Path(self.main_path()).joinpath(Plugin.id_to_file(plugin_id))
|
|
489
|
-
|
|
490
|
-
def _sanitize(self, called_process_error):
|
|
491
|
-
cmd = called_process_error.cmd[:]
|
|
492
|
-
i = 0
|
|
493
|
-
for i in range(len(cmd)):
|
|
494
|
-
if i > 1 and cmd[i - 1] == '--password':
|
|
495
|
-
cmd[i] = '<password>'
|
|
496
|
-
called_process_error.cmd = ' '.join([shlex.quote(c) for c in cmd])
|
|
497
|
-
return called_process_error
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
class MavenPluginSet(PluginSet):
|
|
501
|
-
|
|
502
|
-
TYPE = 'maven'
|
|
503
|
-
|
|
504
|
-
def __init__(self, parsed, path):
|
|
505
|
-
super().__init__(parsed)
|
|
506
|
-
self._built = False
|
|
507
|
-
self._root = path.parent
|
|
508
|
-
|
|
509
|
-
# Returns (jar_path, plugin)
|
|
510
|
-
def build_plugin(self, plugin_id, keystore_path, keystore_alias, keystore_password=None):
|
|
511
|
-
self._big_build(keystore_path, keystore_alias, keystore_password=keystore_password)
|
|
512
|
-
return self._little_build(plugin_id)
|
|
513
|
-
|
|
514
|
-
def has_plugin(self, plugid):
|
|
515
|
-
return self._plugin_path(plugid).is_file()
|
|
516
|
-
|
|
517
|
-
def main(self):
|
|
518
|
-
return self._parsed.get('main', 'src/main/java')
|
|
519
|
-
|
|
520
|
-
def main_path(self):
|
|
521
|
-
return self.root_path().joinpath(self.main())
|
|
522
|
-
|
|
523
|
-
def make_plugin(self, plugid):
|
|
524
|
-
return Plugin.from_path(self._plugin_path(plugid))
|
|
525
|
-
|
|
526
|
-
def root(self):
|
|
527
|
-
return self._root
|
|
528
|
-
|
|
529
|
-
def root_path(self):
|
|
530
|
-
return Path(self.root()).expanduser().resolve()
|
|
531
|
-
|
|
532
|
-
def test(self):
|
|
533
|
-
return self._parsed.get('test', 'src/test/java')
|
|
534
|
-
|
|
535
|
-
def test_path(self):
|
|
536
|
-
return self.root_path().joinpath(self.test())
|
|
537
|
-
|
|
538
|
-
def _big_build(self, keystore_path, keystore_alias, keystore_password=None):
|
|
539
|
-
if not self._built:
|
|
540
|
-
# Do build
|
|
541
|
-
cmd = ['mvn', 'package',
|
|
542
|
-
f'-Dkeystore.file={keystore_path!s}',
|
|
543
|
-
f'-Dkeystore.alias={keystore_alias}',
|
|
544
|
-
f'-Dkeystore.password={keystore_password}']
|
|
545
|
-
try:
|
|
546
|
-
subprocess.run(cmd, cwd=self.root_path(), check=True, stdout=sys.stdout, stderr=sys.stderr)
|
|
547
|
-
except subprocess.CalledProcessError as cpe:
|
|
548
|
-
raise self._sanitize(cpe)
|
|
549
|
-
self._built = True
|
|
550
|
-
|
|
551
|
-
# Returns (jar_path, plugin)
|
|
552
|
-
def _little_build(self, plugin_id):
|
|
553
|
-
jar_path = Path(self.root_path(), 'target', 'pluginjars', f'{plugin_id}.jar')
|
|
554
|
-
if not jar_path.is_file():
|
|
555
|
-
raise Exception(f'{plugin_id}: built JAR not found: {jar_path!s}')
|
|
556
|
-
return (jar_path, Plugin.from_jar(jar_path))
|
|
557
|
-
|
|
558
|
-
def _plugin_path(self, plugin_id):
|
|
559
|
-
return Path(self.main_path()).joinpath(Plugin.id_to_file(plugin_id))
|
|
560
|
-
|
|
561
|
-
def _sanitize(self, called_process_error):
|
|
562
|
-
cmd = called_process_error.cmd[:]
|
|
563
|
-
i = 0
|
|
564
|
-
for i in range(len(cmd)):
|
|
565
|
-
if cmd[i].startswith('-Dkeystore.password='):
|
|
566
|
-
cmd[i] = '-Dkeystore.password=<password>'
|
|
567
|
-
called_process_error.cmd = ' '.join([shlex.quote(c) for c in cmd])
|
|
568
|
-
return called_process_error
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
class Turtles(object):
|
|
572
|
-
|
|
573
|
-
def __init__(self):
|
|
574
|
-
super().__init__()
|
|
575
|
-
self._password = None
|
|
576
|
-
self._plugin_sets = list()
|
|
577
|
-
self._plugin_registries = list()
|
|
578
|
-
self._settings = dict()
|
|
579
|
-
|
|
580
|
-
# Returns plugin_id -> (set_id, jar_path, plugin)
|
|
581
|
-
def build_plugin(self, plugin_ids):
|
|
582
|
-
return {plugin_id: self._build_one_plugin(plugin_id) for plugin_id in plugin_ids}
|
|
583
|
-
|
|
584
|
-
# Returns (src_path, plugin_id) -> list of (registry_id, layer_id, dst_path, plugin)
|
|
585
|
-
def deploy_plugin(self, src_paths, layer_ids, interactive=False):
|
|
586
|
-
plugin_ids = [Plugin.id_from_jar(src_path) for src_path in src_paths]
|
|
587
|
-
return {(src_path, plugin_id): self._deploy_one_plugin(src_path,
|
|
588
|
-
plugin_id,
|
|
589
|
-
layer_ids,
|
|
590
|
-
interactive=interactive) for src_path, plugin_id in zip(src_paths, plugin_ids)}
|
|
591
|
-
|
|
592
|
-
def load_plugin_registries(self, path):
|
|
593
|
-
path = _path(path)
|
|
594
|
-
if not path.is_file():
|
|
595
|
-
raise FileNotFoundError(str(path))
|
|
596
|
-
parsed = None
|
|
597
|
-
with path.open('r') as f:
|
|
598
|
-
parsed = yaml.safe_load(f)
|
|
599
|
-
kind = parsed.get('kind')
|
|
600
|
-
if kind is None:
|
|
601
|
-
raise Exception(f'{path!s}: kind is not defined')
|
|
602
|
-
elif kind != 'Settings':
|
|
603
|
-
raise Exception(f'{path!s}: not of kind Settings: {kind}')
|
|
604
|
-
paths = parsed.get('plugin-registries')
|
|
605
|
-
if paths is None:
|
|
606
|
-
raise Exception(f'{path!s}: undefined plugin-registries')
|
|
607
|
-
self._plugin_registries = list()
|
|
608
|
-
for p in paths:
|
|
609
|
-
self._plugin_registries.extend(PluginRegistry.from_path(p))
|
|
610
|
-
|
|
611
|
-
def load_plugin_sets(self, path):
|
|
612
|
-
path = _path(path)
|
|
613
|
-
if not path.is_file():
|
|
614
|
-
raise FileNotFoundError(str(path))
|
|
615
|
-
parsed = None
|
|
616
|
-
with path.open('r') as f:
|
|
617
|
-
parsed = yaml.safe_load(f)
|
|
618
|
-
kind = parsed.get('kind')
|
|
619
|
-
if kind is None:
|
|
620
|
-
raise Exception(f'{path!s}: kind is not defined')
|
|
621
|
-
elif kind != 'Settings':
|
|
622
|
-
raise Exception(f'{path!s}: not of kind Settings: {kind}')
|
|
623
|
-
paths = parsed.get('plugin-sets')
|
|
624
|
-
if paths is None:
|
|
625
|
-
raise Exception(f'{path!s}: plugin-sets is not defined')
|
|
626
|
-
self.plugin_sets = list()
|
|
627
|
-
for p in paths:
|
|
628
|
-
self._plugin_sets.extend(PluginSet.from_path(p))
|
|
629
|
-
|
|
630
|
-
def load_settings(self, path):
|
|
631
|
-
path = _path(path)
|
|
632
|
-
if not path.is_file():
|
|
633
|
-
raise FileNotFoundError(str(path))
|
|
634
|
-
with path.open('r') as f:
|
|
635
|
-
parsed = yaml.safe_load(f)
|
|
636
|
-
kind = parsed.get('kind')
|
|
637
|
-
if kind is None:
|
|
638
|
-
raise Exception(f'{path!s}: kind is not defined')
|
|
639
|
-
elif kind != 'Settings':
|
|
640
|
-
raise Exception(f'{path!s}: not of kind Settings: {kind}')
|
|
641
|
-
self._settings = parsed
|
|
642
|
-
|
|
643
|
-
# Returns plugin_id -> list of (registry_id, layer_id, dst_path, plugin)
|
|
644
|
-
def release_plugin(self, plugin_ids, layer_ids, interactive=False):
|
|
645
|
-
# ... plugin_id -> (set_id, jar_path, plugin)
|
|
646
|
-
ret1 = self.build_plugin(plugin_ids)
|
|
647
|
-
jar_paths = [jar_path for set_id, jar_path, plugin in ret1.values()]
|
|
648
|
-
# ... (src_path, plugin_id) -> list of (registry_id, layer_id, dst_path, plugin)
|
|
649
|
-
ret2 = self.deploy_plugin(jar_paths,
|
|
650
|
-
layer_ids,
|
|
651
|
-
interactive=interactive)
|
|
652
|
-
return {plugin_id: val for (jar_path, plugin_id), val in ret2.items()}
|
|
653
|
-
|
|
654
|
-
def set_password(self, obj):
|
|
655
|
-
self._password = obj() if callable(obj) else obj
|
|
656
|
-
|
|
657
|
-
# Returns (set_id, jar_path, plugin)
|
|
658
|
-
def _build_one_plugin(self, plugin_id):
|
|
659
|
-
"""
|
|
660
|
-
Returns a (plugsetid, plujarpath) tuple
|
|
661
|
-
"""
|
|
662
|
-
for plugin_set in self._plugin_sets:
|
|
663
|
-
if plugin_set.has_plugin(plugin_id):
|
|
664
|
-
return (plugin_set.id(),
|
|
665
|
-
*plugin_set.build_plugin(plugin_id,
|
|
666
|
-
self._get_plugin_signing_keystore(),
|
|
667
|
-
self._get_plugin_signing_alias(),
|
|
668
|
-
self._get_plugin_signing_password()))
|
|
669
|
-
raise Exception(f'{plugin_id}: not found in any plugin set')
|
|
670
|
-
|
|
671
|
-
# Returns list of (registry_id, layer_id, dst_path, plugin)
|
|
672
|
-
def _deploy_one_plugin(self, src_jar, plugin_id, layer_ids, interactive=False):
|
|
673
|
-
ret = list()
|
|
674
|
-
for plugin_registry in self._plugin_registries:
|
|
675
|
-
if plugin_registry.has_plugin(plugin_id):
|
|
676
|
-
for layer_id in layer_ids:
|
|
677
|
-
layer = plugin_registry.get_layer(layer_id)
|
|
678
|
-
if layer is not None:
|
|
679
|
-
ret.append((plugin_registry.id(),
|
|
680
|
-
layer.id(),
|
|
681
|
-
*layer.deploy_plugin(plugin_id,
|
|
682
|
-
src_jar,
|
|
683
|
-
interactive=interactive)))
|
|
684
|
-
if len(ret) == 0:
|
|
685
|
-
raise Exception(f'{src_jar}: {plugin_id} not declared in any plugin registry')
|
|
686
|
-
return ret
|
|
687
|
-
|
|
688
|
-
def _get_plugin_signing_alias(self):
|
|
689
|
-
ret = self._settings.get('plugin-signing-alias')
|
|
690
|
-
if ret is None:
|
|
691
|
-
raise Exception('plugin-signing-alias is not defined in the settings')
|
|
692
|
-
return ret
|
|
693
|
-
|
|
694
|
-
def _get_plugin_signing_keystore(self):
|
|
695
|
-
ret = self._settings.get('plugin-signing-keystore')
|
|
696
|
-
if ret is None:
|
|
697
|
-
raise Exception('plugin-signing-keystore is not defined in the settings')
|
|
698
|
-
return _path(ret)
|
|
699
|
-
|
|
700
|
-
def _get_plugin_signing_password(self):
|
|
701
|
-
return self._password
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
class TurtlesCli(Turtles):
|
|
705
|
-
|
|
706
|
-
XDG_CONFIG_DIR=xdg.xdg_config_home().joinpath(PROG)
|
|
707
|
-
GLOBAL_CONFIG_DIR=Path('/etc', PROG)
|
|
708
|
-
CONFIG_DIRS=[XDG_CONFIG_DIR, GLOBAL_CONFIG_DIR]
|
|
709
|
-
|
|
710
|
-
PLUGIN_REGISTRIES='plugin-registries.yaml'
|
|
711
|
-
PLUGIN_SETS='plugin-sets.yaml'
|
|
712
|
-
SETTINGS='settings.yaml'
|
|
713
|
-
|
|
714
|
-
@staticmethod
|
|
715
|
-
def _config_files(name):
|
|
716
|
-
return [Path(base, name) for base in TurtlesCli.CONFIG_DIRS]
|
|
717
|
-
|
|
718
|
-
@staticmethod
|
|
719
|
-
def _list_config_files(name):
|
|
720
|
-
return ' or '.join(str(x) for x in TurtlesCli._config_files(name))
|
|
721
|
-
|
|
722
|
-
@staticmethod
|
|
723
|
-
def _select_config_file(name):
|
|
724
|
-
for x in TurtlesCli._config_files(name):
|
|
725
|
-
if x.is_file():
|
|
726
|
-
return x
|
|
727
|
-
return None
|
|
728
|
-
|
|
729
|
-
def __init__(self):
|
|
730
|
-
super().__init__()
|
|
731
|
-
self._args = None
|
|
732
|
-
self._identifiers = None
|
|
733
|
-
self._jars = None
|
|
734
|
-
self._layers = None
|
|
735
|
-
self._parser = None
|
|
736
|
-
self._subparsers = None
|
|
737
|
-
|
|
738
|
-
def run(self):
|
|
739
|
-
self._make_parser()
|
|
740
|
-
self._args = self._parser.parse_args()
|
|
741
|
-
if self._args.debug_cli:
|
|
742
|
-
print(self._args)
|
|
743
|
-
self._args.fun()
|
|
744
|
-
|
|
745
|
-
def _analyze_registry(self):
|
|
746
|
-
# Prerequisites
|
|
747
|
-
self.load_settings(self._args.settings or TurtlesCli._select_config_file(TurtlesCli.SETTINGS))
|
|
748
|
-
self.load_plugin_registries(self._args.plugin_registries or TurtlesCli._select_config_file(TurtlesCli.PLUGIN_REGISTRIES))
|
|
749
|
-
self.load_plugin_sets(self._args.plugin_sets or TurtlesCli._select_config_file(TurtlesCli.PLUGIN_SETS))
|
|
750
|
-
|
|
751
|
-
#####
|
|
752
|
-
title = 'Plugins declared in a plugin registry but not found in any plugin set'
|
|
753
|
-
result = list()
|
|
754
|
-
headers = ['Plugin registry', 'Plugin identifier']
|
|
755
|
-
for plugin_registry in self._plugin_registries:
|
|
756
|
-
for plugin_id in plugin_registry.plugin_identifiers():
|
|
757
|
-
for plugin_set in self._plugin_sets:
|
|
758
|
-
if plugin_set.has_plugin(plugin_id):
|
|
759
|
-
break
|
|
760
|
-
else: # No plugin set matched
|
|
761
|
-
result.append([plugin_registry.id(), plugin_id])
|
|
762
|
-
if len(result) > 0:
|
|
763
|
-
self._tabulate(title, result, headers)
|
|
764
|
-
|
|
765
|
-
#####
|
|
766
|
-
title = 'Plugins declared in a plugin registry but with missing JARs'
|
|
767
|
-
result = list()
|
|
768
|
-
headers = ['Plugin registry', 'Plugin registry layer', 'Plugin identifier']
|
|
769
|
-
for plugin_registry in self._plugin_registries:
|
|
770
|
-
for plugin_id in plugin_registry.plugin_identifiers():
|
|
771
|
-
for layer_id in plugin_registry.get_layer_ids():
|
|
772
|
-
if plugin_registry.get_layer(layer_id).get_file_for(plugin_id) is None:
|
|
773
|
-
result.append([plugin_registry.id(), layer_id, plugin_id])
|
|
774
|
-
if len(result) > 0:
|
|
775
|
-
self._tabulate(title, result, headers)
|
|
776
|
-
|
|
777
|
-
#####
|
|
778
|
-
title = 'Plugin JARs not declared in any plugin registry'
|
|
779
|
-
result = list()
|
|
780
|
-
headers = ['Plugin registry', 'Plugin registry layer', 'Plugin JAR', 'Plugin identifier']
|
|
781
|
-
# Map from layer path to the layers that have that path
|
|
782
|
-
pathlayers = dict()
|
|
783
|
-
for plugin_registry in self._plugin_registries:
|
|
784
|
-
for layer_id in plugin_registry.get_layer_ids():
|
|
785
|
-
layer_id = plugin_registry.get_layer(layer_id)
|
|
786
|
-
path = layer_id.path()
|
|
787
|
-
pathlayers.setdefault(path, list()).append(layer_id)
|
|
788
|
-
# Do report, taking care of not processing a path twice if overlapping
|
|
789
|
-
visited = set()
|
|
790
|
-
for plugin_registry in self._plugin_registries:
|
|
791
|
-
for layer_id in plugin_registry.get_layer_ids():
|
|
792
|
-
layer_id = plugin_registry.get_layer(layer_id)
|
|
793
|
-
if layer_id.path() not in visited:
|
|
794
|
-
visited.add(layer_id.path())
|
|
795
|
-
for jar_path in layer_id.get_jars():
|
|
796
|
-
if jar_path.stat().st_size > 0:
|
|
797
|
-
plugin_id = Plugin.id_from_jar(jar_path)
|
|
798
|
-
if not any([lay.plugin_registry().has_plugin(plugin_id) for lay in pathlayers[layer_id.path()]]):
|
|
799
|
-
result.append([plugin_registry.id(), layer_id, jar_path, plugin_id])
|
|
800
|
-
if len(result) > 0:
|
|
801
|
-
self._tabulate(title, result, headers)
|
|
802
|
-
|
|
803
|
-
def _build_plugin(self):
|
|
804
|
-
# Prerequisites
|
|
805
|
-
self.load_settings(self._args.settings or TurtlesCli._select_config_file(TurtlesCli.SETTINGS))
|
|
806
|
-
self.load_plugin_sets(self._args.plugin_sets or TurtlesCli._select_config_file(TurtlesCli.PLUGIN_SETS))
|
|
807
|
-
self._obtain_password()
|
|
808
|
-
# Action
|
|
809
|
-
# ... plugin_id -> (set_id, jar_path, plugin)
|
|
810
|
-
ret = self.build_plugin(self._get_identifiers())
|
|
811
|
-
# Output
|
|
812
|
-
print(tabulate.tabulate([[plugin_id, plugin.version(), set_id, jar_path] for plugin_id, (set_id, jar_path, plugin) in ret.items()],
|
|
813
|
-
headers=['Plugin identifier', 'Plugin version', 'Plugin set', 'Plugin JAR'],
|
|
814
|
-
tablefmt=self._args.output_format))
|
|
815
|
-
|
|
816
|
-
def _copyright(self):
|
|
817
|
-
print(__copyright__)
|
|
818
|
-
|
|
819
|
-
def _deploy_plugin(self):
|
|
820
|
-
# Prerequisites
|
|
821
|
-
self.load_plugin_registries(self._args.plugin_registries or TurtlesCli._select_config_file(TurtlesCli.PLUGIN_REGISTRIES))
|
|
822
|
-
# Action
|
|
823
|
-
# ... (src_path, plugin_id) -> list of (registry_id, layer_id, dst_path, plugin)
|
|
824
|
-
ret = self.deploy_plugin(self._get_jars(),
|
|
825
|
-
self._get_layers(),
|
|
826
|
-
interactive=self._args.interactive)
|
|
827
|
-
# Output
|
|
828
|
-
print(tabulate.tabulate([[src_path, plugin_id, plugin.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],
|
|
829
|
-
headers=['Plugin JAR', 'Plugin identifier', 'Plugin version', 'Plugin registry', 'Plugin registry layer', 'Deployed JAR'],
|
|
830
|
-
tablefmt=self._args.output_format))
|
|
831
|
-
|
|
832
|
-
def _get_identifiers(self):
|
|
833
|
-
if self._identifiers is None:
|
|
834
|
-
self._identifiers = list()
|
|
835
|
-
self._identifiers.extend(self._args.remainder)
|
|
836
|
-
self._identifiers.extend(self._args.identifier)
|
|
837
|
-
for path in self._args.identifiers:
|
|
838
|
-
self._identifiers.extend(_file_lines(path))
|
|
839
|
-
if len(self._identifiers) == 0:
|
|
840
|
-
self._parser.error('list of plugin identifiers to build is empty')
|
|
841
|
-
return self._identifiers
|
|
842
|
-
|
|
843
|
-
def _get_jars(self):
|
|
844
|
-
if self._jars is None:
|
|
845
|
-
self._jars = list()
|
|
846
|
-
self._jars.extend(self._args.remainder)
|
|
847
|
-
self._jars.extend(self._args.jar)
|
|
848
|
-
for path in self._args.jars:
|
|
849
|
-
self._jars.extend(_file_lines(path))
|
|
850
|
-
if len(self._jars) == 0:
|
|
851
|
-
self._parser.error('list of plugin JARs to deploy is empty')
|
|
852
|
-
return self._jars
|
|
853
|
-
|
|
854
|
-
def _get_layers(self):
|
|
855
|
-
if self._layers is None:
|
|
856
|
-
self._layers = list()
|
|
857
|
-
self._layers.extend(self._args.layer)
|
|
858
|
-
for path in self._args.layers:
|
|
859
|
-
self._layers.extend(_file_lines(path))
|
|
860
|
-
if len(self._layers) == 0:
|
|
861
|
-
self._parser.error('list of plugin registry layers to process is empty')
|
|
862
|
-
return self._layers
|
|
863
|
-
|
|
864
|
-
def _license(self):
|
|
865
|
-
print(__license__)
|
|
866
|
-
|
|
867
|
-
def _make_option_debug_cli(self, container):
|
|
868
|
-
container.add_argument('--debug-cli',
|
|
869
|
-
action='store_true',
|
|
870
|
-
help='print the result of parsing command line arguments')
|
|
871
|
-
|
|
872
|
-
def _make_option_non_interactive(self, container):
|
|
873
|
-
container.add_argument('--non-interactive', '-n',
|
|
874
|
-
dest='interactive',
|
|
875
|
-
action='store_false', # note: default True
|
|
876
|
-
help='disallow interactive prompts (default: allow)')
|
|
877
|
-
|
|
878
|
-
def _make_option_output_format(self, container):
|
|
879
|
-
container.add_argument('--output-format',
|
|
880
|
-
metavar='FMT',
|
|
881
|
-
choices=tabulate.tabulate_formats,
|
|
882
|
-
default='simple',
|
|
883
|
-
help='set tabular output format to %(metavar)s (default: %(default)s; choices: %(choices)s)')
|
|
884
|
-
|
|
885
|
-
def _make_option_password(self, container):
|
|
886
|
-
container.add_argument('--password',
|
|
887
|
-
metavar='PASS',
|
|
888
|
-
help='set the plugin signing password')
|
|
889
|
-
|
|
890
|
-
def _make_option_plugin_registries(self, container):
|
|
891
|
-
container.add_argument('--plugin-registries',
|
|
892
|
-
metavar='FILE',
|
|
893
|
-
type=Path,
|
|
894
|
-
help=f'load plugin registries from %(metavar)s (default: {TurtlesCli._list_config_files(TurtlesCli.PLUGIN_REGISTRIES)})')
|
|
895
|
-
|
|
896
|
-
def _make_option_plugin_sets(self, container):
|
|
897
|
-
container.add_argument('--plugin-sets',
|
|
898
|
-
metavar='FILE',
|
|
899
|
-
type=Path,
|
|
900
|
-
help=f'load plugin sets from %(metavar)s (default: {TurtlesCli._list_config_files(TurtlesCli.PLUGIN_SETS)})')
|
|
901
|
-
|
|
902
|
-
def _make_option_production(self, container):
|
|
903
|
-
container.add_argument('--production', '-p',
|
|
904
|
-
dest='layer',
|
|
905
|
-
action='append_const',
|
|
906
|
-
const=PluginRegistryLayer.PRODUCTION,
|
|
907
|
-
help="synonym for --layer=%(const)s (i.e. add '%(const)s' to the list of plugin registry layers to process)")
|
|
908
|
-
|
|
909
|
-
def _make_option_settings(self, container):
|
|
910
|
-
container.add_argument('--settings',
|
|
911
|
-
metavar='FILE',
|
|
912
|
-
type=Path,
|
|
913
|
-
help=f'load settings from %(metavar)s (default: {TurtlesCli._list_config_files(TurtlesCli.SETTINGS)})')
|
|
914
|
-
|
|
915
|
-
def _make_option_testing(self, container):
|
|
916
|
-
container.add_argument('--testing', '-t',
|
|
917
|
-
dest='layer',
|
|
918
|
-
action='append_const',
|
|
919
|
-
const=PluginRegistryLayer.TESTING,
|
|
920
|
-
help="synonym for --layer=%(const)s (i.e. add '%(const)s' to the list of plugin registry layers to process)")
|
|
921
|
-
|
|
922
|
-
def _make_options_identifiers(self, container):
|
|
923
|
-
container.add_argument('--identifier', '-i',
|
|
924
|
-
metavar='PLUGID',
|
|
925
|
-
action='append',
|
|
926
|
-
default=list(),
|
|
927
|
-
help='add %(metavar)s to the list of plugin identifiers to build')
|
|
928
|
-
container.add_argument('--identifiers', '-I',
|
|
929
|
-
metavar='FILE',
|
|
930
|
-
action='append',
|
|
931
|
-
default=list(),
|
|
932
|
-
help='add the plugin identifiers in %(metavar)s to the list of plugin identifiers to build')
|
|
933
|
-
container.add_argument('remainder',
|
|
934
|
-
metavar='PLUGID',
|
|
935
|
-
nargs='*',
|
|
936
|
-
help='plugin identifier to build')
|
|
937
|
-
|
|
938
|
-
def _make_options_jars(self, container):
|
|
939
|
-
container.add_argument('--jar', '-j',
|
|
940
|
-
metavar='PLUGJAR',
|
|
941
|
-
type=Path,
|
|
942
|
-
action='append',
|
|
943
|
-
default=list(),
|
|
944
|
-
help='add %(metavar)s to the list of plugin JARs to deploy')
|
|
945
|
-
container.add_argument('--jars', '-J',
|
|
946
|
-
metavar='FILE',
|
|
947
|
-
action='append',
|
|
948
|
-
default=list(),
|
|
949
|
-
help='add the plugin JARs in %(metavar)s to the list of plugin JARs to deploy')
|
|
950
|
-
container.add_argument('remainder',
|
|
951
|
-
metavar='PLUGJAR',
|
|
952
|
-
nargs='*',
|
|
953
|
-
help='plugin JAR to deploy')
|
|
954
|
-
|
|
955
|
-
def _make_options_layers(self, container):
|
|
956
|
-
container.add_argument('--layer', '-l',
|
|
957
|
-
metavar='LAYER',
|
|
958
|
-
action='append',
|
|
959
|
-
default=list(),
|
|
960
|
-
help='add %(metavar)s to the list of plugin registry layers to process')
|
|
961
|
-
container.add_argument('--layers', '-L',
|
|
962
|
-
metavar='FILE',
|
|
963
|
-
action='append',
|
|
964
|
-
default=list(),
|
|
965
|
-
help='add the layers in %(metavar)s to the list of plugin registry layers to process')
|
|
966
|
-
|
|
967
|
-
def _make_parser(self):
|
|
968
|
-
self._parser = argparse.ArgumentParser(prog=PROG)
|
|
969
|
-
self._subparsers = self._parser.add_subparsers(title='commands',
|
|
970
|
-
description="Add --help to see the command's own help message",
|
|
971
|
-
# In subparsers, metavar is also used as the heading of the column of subcommands
|
|
972
|
-
metavar='COMMAND',
|
|
973
|
-
# In subparsers, help is used as the heading of the column of subcommand descriptions
|
|
974
|
-
help='DESCRIPTION')
|
|
975
|
-
self._make_option_debug_cli(self._parser)
|
|
976
|
-
self._make_option_non_interactive(self._parser)
|
|
977
|
-
self._make_option_output_format(self._parser)
|
|
978
|
-
self._make_parser_analyze_registry(self._subparsers)
|
|
979
|
-
self._make_parser_build_plugin(self._subparsers)
|
|
980
|
-
self._make_parser_copyright(self._subparsers)
|
|
981
|
-
self._make_parser_deploy_plugin(self._subparsers)
|
|
982
|
-
self._make_parser_license(self._subparsers)
|
|
983
|
-
self._make_parser_release_plugin(self._subparsers)
|
|
984
|
-
self._make_parser_usage(self._subparsers)
|
|
985
|
-
self._make_parser_version(self._subparsers)
|
|
986
|
-
|
|
987
|
-
def _make_parser_analyze_registry(self, container):
|
|
988
|
-
parser = container.add_parser('analyze-registry', aliases=['ar'],
|
|
989
|
-
description='Analyze plugin registries',
|
|
990
|
-
help='analyze plugin registries')
|
|
991
|
-
parser.set_defaults(fun=self._analyze_registry)
|
|
992
|
-
self._make_option_plugin_registries(parser)
|
|
993
|
-
self._make_option_plugin_sets(parser)
|
|
994
|
-
self._make_option_settings(parser)
|
|
995
|
-
|
|
996
|
-
def _make_parser_build_plugin(self, container):
|
|
997
|
-
parser = container.add_parser('build-plugin', aliases=['bp'],
|
|
998
|
-
description='Build (package and sign) plugins',
|
|
999
|
-
help='build (package and sign) plugins')
|
|
1000
|
-
parser.set_defaults(fun=self._build_plugin)
|
|
1001
|
-
self._make_options_identifiers(parser)
|
|
1002
|
-
self._make_option_password(parser)
|
|
1003
|
-
self._make_option_plugin_sets(parser)
|
|
1004
|
-
self._make_option_settings(parser)
|
|
1005
|
-
|
|
1006
|
-
def _make_parser_copyright(self, container):
|
|
1007
|
-
parser = container.add_parser('copyright',
|
|
1008
|
-
description='Show copyright and exit',
|
|
1009
|
-
help='show copyright and exit')
|
|
1010
|
-
parser.set_defaults(fun=self._copyright)
|
|
1011
|
-
|
|
1012
|
-
def _make_parser_deploy_plugin(self, container):
|
|
1013
|
-
parser = container.add_parser('deploy-plugin', aliases=['dp'],
|
|
1014
|
-
description='Deploy plugins',
|
|
1015
|
-
help='deploy plugins')
|
|
1016
|
-
parser.set_defaults(fun=self._deploy_plugin)
|
|
1017
|
-
self._make_options_jars(parser)
|
|
1018
|
-
self._make_options_layers(parser)
|
|
1019
|
-
self._make_option_plugin_registries(parser)
|
|
1020
|
-
self._make_option_production(parser)
|
|
1021
|
-
self._make_option_testing(parser)
|
|
1022
|
-
|
|
1023
|
-
def _make_parser_license(self, container):
|
|
1024
|
-
parser = container.add_parser('license',
|
|
1025
|
-
description='Show license and exit',
|
|
1026
|
-
help='show license and exit')
|
|
1027
|
-
parser.set_defaults(fun=self._license)
|
|
1028
|
-
|
|
1029
|
-
def _make_parser_release_plugin(self, container):
|
|
1030
|
-
parser = container.add_parser('release-plugin', aliases=['rp'],
|
|
1031
|
-
description='Release (build and deploy) plugins',
|
|
1032
|
-
help='release (build and deploy) plugins')
|
|
1033
|
-
parser.set_defaults(fun=self._release_plugin)
|
|
1034
|
-
self._make_options_identifiers(parser)
|
|
1035
|
-
self._make_options_layers(parser)
|
|
1036
|
-
self._make_option_password(parser)
|
|
1037
|
-
self._make_option_plugin_registries(parser)
|
|
1038
|
-
self._make_option_plugin_sets(parser)
|
|
1039
|
-
self._make_option_production(parser)
|
|
1040
|
-
self._make_option_settings(parser)
|
|
1041
|
-
self._make_option_testing(parser)
|
|
1042
|
-
|
|
1043
|
-
def _make_parser_usage(self, container):
|
|
1044
|
-
parser = container.add_parser('usage',
|
|
1045
|
-
description='Show usage and exit',
|
|
1046
|
-
help='show detailed usage and exit')
|
|
1047
|
-
parser.set_defaults(fun=self._usage)
|
|
1048
|
-
|
|
1049
|
-
def _make_parser_version(self, container):
|
|
1050
|
-
parser = container.add_parser('version',
|
|
1051
|
-
description='Show version and exit',
|
|
1052
|
-
help='show version and exit')
|
|
1053
|
-
parser.set_defaults(fun=self._version)
|
|
1054
|
-
|
|
1055
|
-
def _obtain_password(self):
|
|
1056
|
-
if self._args.password is not None:
|
|
1057
|
-
_p = self._args.password
|
|
1058
|
-
elif self._args.interactive:
|
|
1059
|
-
_p = getpass.getpass('Plugin signing password: ')
|
|
1060
|
-
else:
|
|
1061
|
-
self._parser.error('no plugin signing password specified while in non-interactive mode')
|
|
1062
|
-
self.set_password(lambda: _p)
|
|
1063
|
-
|
|
1064
|
-
def _release_plugin(self):
|
|
1065
|
-
# Prerequisites
|
|
1066
|
-
self.load_settings(self._args.settings or TurtlesCli._select_config_file(TurtlesCli.SETTINGS))
|
|
1067
|
-
self.load_plugin_sets(self._args.plugin_sets or TurtlesCli._select_config_file(TurtlesCli.PLUGIN_SETS))
|
|
1068
|
-
self.load_plugin_registries(self._args.plugin_registries or TurtlesCli._select_config_file(TurtlesCli.PLUGIN_REGISTRIES))
|
|
1069
|
-
self._obtain_password()
|
|
1070
|
-
# Action
|
|
1071
|
-
# ... plugin_id -> list of (registry_id, layer_id, dst_path, plugin)
|
|
1072
|
-
ret = self.release_plugin(self._get_identifiers(),
|
|
1073
|
-
self._get_layers(),
|
|
1074
|
-
interactive=self._args.interactive)
|
|
1075
|
-
# Output
|
|
1076
|
-
print(tabulate.tabulate([[plugin_id, plugin.version(), registry_id, layer_id, dst_path] for plugin_id, val in ret.items() for registry_id, layer_id, dst_path, plugin in val],
|
|
1077
|
-
headers=['Plugin identifier', 'Plugin version', 'Plugin registry', 'Plugin registry layer', 'Deployed JAR'],
|
|
1078
|
-
tablefmt=self._args.output_format))
|
|
1079
|
-
|
|
1080
|
-
def _tabulate(self, title, data, headers):
|
|
1081
|
-
print(self._title(title))
|
|
1082
|
-
print(tabulate.tabulate(data, headers=headers, tablefmt=self._args.output_format))
|
|
1083
|
-
print()
|
|
1084
|
-
|
|
1085
|
-
def _title(self, s):
|
|
1086
|
-
return f'{"=" * len(s)}\n{s}\n{"=" * len(s)}\n'
|
|
1087
|
-
|
|
1088
|
-
def _usage(self):
|
|
1089
|
-
self._parser.print_usage()
|
|
1090
|
-
print()
|
|
1091
|
-
uniq = set()
|
|
1092
|
-
for cmd, par in self._subparsers.choices.items():
|
|
1093
|
-
if par not in uniq:
|
|
1094
|
-
uniq.add(par)
|
|
1095
|
-
for s in par.format_usage().split('\n'):
|
|
1096
|
-
usage = 'usage: '
|
|
1097
|
-
print(f'{" " * len(usage)}{s[len(usage):]}' if s.startswith(usage) else s)
|
|
1098
|
-
|
|
1099
|
-
def _version(self):
|
|
1100
|
-
print(__version__)
|
|
1101
|
-
|
|
1102
|
-
#
|
|
1103
|
-
# Main entry point
|
|
1104
|
-
#
|
|
1105
|
-
|
|
1106
|
-
if __name__ == '__main__':
|
|
1107
|
-
if sys.version_info < (3, 6):
|
|
1108
|
-
sys.exit('Requires Python 3.6 or greater; currently {}'.format(sys.version))
|
|
1109
|
-
TurtlesCli().run()
|