lockss-turtles 0.5.0.dev4__py3-none-any.whl → 0.6.0.dev2__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.
@@ -1,37 +1,14 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- # Copyright (c) 2000-2023, Board of Trustees of Leland Stanford Jr. University
4
- #
5
- # Redistribution and use in source and binary forms, with or without
6
- # modification, are permitted provided that the following conditions are met:
7
- #
8
- # 1. Redistributions of source code must retain the above copyright notice,
9
- # this list of conditions and the following disclaimer.
10
- #
11
- # 2. Redistributions in binary form must reproduce the above copyright notice,
12
- # this list of conditions and the following disclaimer in the documentation
13
- # and/or other materials provided with the distribution.
14
- #
15
- # 3. Neither the name of the copyright holder nor the names of its contributors
16
- # may be used to endorse or promote products derived from this software without
17
- # specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
30
-
31
- __version__ = '0.5.0-dev4'
3
+ """
4
+ Library and command line tool to manage LOCKSS plugin sets and LOCKSS plugin
5
+ registries.
6
+ """
7
+
8
+ __version__ = '0.6.0-dev2'
32
9
 
33
10
  __copyright__ = '''
34
- Copyright (c) 2000-2023, Board of Trustees of Leland Stanford Jr. University
11
+ Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
35
12
  '''.strip()
36
13
 
37
14
  __license__ = __copyright__ + '\n\n' + '''
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- # Copyright (c) 2000-2023, Board of Trustees of Leland Stanford Jr. University
3
+ # Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -28,6 +28,6 @@
28
28
  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
29
  # POSSIBILITY OF SUCH DAMAGE.
30
30
 
31
- import lockss.turtles.cli
31
+ from lockss.turtles.cli import main
32
32
 
33
- lockss.turtles.cli.main()
33
+ main()
lockss/turtles/app.py CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- # Copyright (c) 2000-2023, Board of Trustees of Leland Stanford Jr. University
3
+ # Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
4
4
  #
5
5
  # Redistribution and use in source and binary forms, with or without
6
6
  # modification, are permitted provided that the following conditions are met:
@@ -28,103 +28,79 @@
28
28
  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
29
  # POSSIBILITY OF SUCH DAMAGE.
30
30
 
31
- import importlib.resources
31
+ from collections.abc import Callable
32
+ import importlib.resources as IR
32
33
  from pathlib import Path
34
+ from typing import Dict, List, Optional, Tuple, Union
33
35
 
36
+ from lockss.pybasic.fileutil import path
34
37
  import xdg
35
38
 
36
- from lockss.turtles.plugin import Plugin
37
- from lockss.turtles.plugin_registry import PluginRegistry, PluginRegistryCatalog
38
- from lockss.turtles.plugin_set import PluginSet, PluginSetCatalog
39
- import lockss.turtles.resources
40
- from lockss.turtles.util import _load_and_validate, _path
39
+ from . import resources as __resources__
40
+ from .plugin import Plugin
41
+ from .plugin_registry import PluginRegistry, PluginRegistryCatalog
42
+ from .plugin_set import PluginSet, PluginSetCatalog
43
+ from .util import YamlT, load_and_validate
41
44
 
42
45
 
43
46
  class TurtlesApp(object):
44
47
 
45
- XDG_CONFIG_DIR = xdg.xdg_config_home().joinpath(__package__)
48
+ CONFIG_DIR_NAME = 'lockss-turtles'
46
49
 
47
- USR_CONFIG_DIR = Path('/usr/local/share', __package__)
50
+ XDG_CONFIG_DIR: Path = xdg.xdg_config_home().joinpath(CONFIG_DIR_NAME)
48
51
 
49
- ETC_CONFIG_DIR = Path('/etc', __package__)
52
+ USR_CONFIG_DIR: Path = Path('/usr/local/share', CONFIG_DIR_NAME)
50
53
 
51
- CONFIG_DIRS = [XDG_CONFIG_DIR, USR_CONFIG_DIR, ETC_CONFIG_DIR]
54
+ ETC_CONFIG_DIR: Path = Path('/etc', CONFIG_DIR_NAME)
52
55
 
53
- PLUGIN_REGISTRY_CATALOG = 'plugin-registry-catalog.yaml'
56
+ CONFIG_DIRS: List[Path] = [XDG_CONFIG_DIR, USR_CONFIG_DIR, ETC_CONFIG_DIR]
54
57
 
55
- PLUGIN_SET_CATALOG = 'plugin-set-catalog.yaml'
58
+ PLUGIN_REGISTRY_CATALOG: str = 'plugin-registry-catalog.yaml'
56
59
 
57
- PLUGIN_SIGNING_CREDENTIALS = 'plugin-signing-credentials.yaml'
60
+ PLUGIN_SET_CATALOG: str = 'plugin-set-catalog.yaml'
58
61
 
59
- PLUGIN_SIGNING_CREDENTIALS_SCHEMA = 'plugin-signing-credentials-schema.json'
62
+ PLUGIN_SIGNING_CREDENTIALS: str = 'plugin-signing-credentials.yaml'
60
63
 
61
- @staticmethod
62
- def _default_files(file_str):
63
- return [dir_path.joinpath(file_str) for dir_path in TurtlesApp.CONFIG_DIRS]
64
-
65
- @staticmethod
66
- def _select_file(file_str, preselected=None):
67
- if preselected:
68
- preselected = _path(preselected)
69
- if not preselected.is_file():
70
- raise FileNotFoundError(str(preselected))
71
- return preselected
72
- choices = TurtlesApp._default_files(file_str)
73
- ret = next(filter(Path.is_file, choices), None)
74
- if ret is None:
75
- raise FileNotFoundError(' or '.join(map(str, choices)))
76
- return ret
64
+ PLUGIN_SIGNING_CREDENTIALS_SCHEMA: str = 'plugin-signing-credentials-schema.json'
77
65
 
78
- def __init__(self):
66
+ def __init__(self) -> None:
79
67
  super().__init__()
80
- self._password = None
81
- self._plugin_registries = None
82
- self._plugin_sets = None
83
- self._plugin_signing_credentials = None
68
+ self._password: Optional[Callable[[], str]] = None
69
+ self._plugin_registries: Optional[List[PluginRegistry]] = None
70
+ self._plugin_sets: Optional[List[PluginSet]] = None
71
+ self._plugin_signing_credentials: YamlT = None
84
72
 
85
- # Returns plugin_id -> (set_id, jar_path, plugin)
86
- def build_plugin(self, plugin_ids):
73
+ def build_plugin(self, plugin_ids: List[str]) -> Dict[str, Tuple[str, Path, Plugin]]:
87
74
  return {plugin_id: self._build_one_plugin(plugin_id) for plugin_id in plugin_ids}
88
75
 
89
- def default_plugin_registry_catalogs(self):
90
- return TurtlesApp._default_files(TurtlesApp.PLUGIN_REGISTRY_CATALOG)
91
-
92
- def default_plugin_set_catalogs(self):
93
- return TurtlesApp._default_files(TurtlesApp.PLUGIN_SET_CATALOG)
94
-
95
- def default_plugin_signing_credentials(self):
96
- return TurtlesApp._default_files(TurtlesApp.PLUGIN_SIGNING_CREDENTIALS)
97
-
98
- # Returns (src_path, plugin_id) -> list of (registry_id, layer_id, dst_path, plugin)
99
- def deploy_plugin(self, src_paths, layer_ids, interactive=False):
76
+ def deploy_plugin(self, src_paths: List[Path], layer_ids: List[str], interactive: bool=False) -> Dict[Tuple[Path, str], List[Tuple[str, str, Optional[Path], Optional[Plugin]]]]:
100
77
  plugin_ids = [Plugin.id_from_jar(src_path) for src_path in src_paths]
101
78
  return {(src_path, plugin_id): self._deploy_one_plugin(src_path,
102
79
  plugin_id,
103
80
  layer_ids,
104
81
  interactive=interactive) for src_path, plugin_id in zip(src_paths, plugin_ids)}
105
82
 
106
- def load_plugin_registries(self, plugin_registry_catalog_path=None):
83
+ def load_plugin_registries(self, plugin_registry_catalog_path: Optional[Union[Path, str]]=None) -> None:
107
84
  if self._plugin_registries is None:
108
85
  plugin_registry_catalog = PluginRegistryCatalog.from_path(self.select_plugin_registry_catalog(plugin_registry_catalog_path))
109
86
  self._plugin_registries = list()
110
87
  for plugin_registry_file in plugin_registry_catalog.get_plugin_registry_files():
111
88
  self._plugin_registries.extend(PluginRegistry.from_path(plugin_registry_file))
112
89
 
113
- def load_plugin_sets(self, plugin_set_catalog_path=None):
90
+ def load_plugin_sets(self, plugin_set_catalog_path: Optional[Union[Path, str]]=None) -> None:
114
91
  if self._plugin_sets is None:
115
92
  plugin_set_catalog = PluginSetCatalog.from_path(self.select_plugin_set_catalog(plugin_set_catalog_path))
116
93
  self._plugin_sets = list()
117
94
  for plugin_set_file in plugin_set_catalog.get_plugin_set_files():
118
95
  self._plugin_sets.extend(PluginSet.from_path(plugin_set_file))
119
96
 
120
- def load_plugin_signing_credentials(self, plugin_signing_credentials_path=None):
97
+ def load_plugin_signing_credentials(self, plugin_signing_credentials_path: Optional[Union[Path, str]]=None) -> None:
121
98
  if self._plugin_signing_credentials is None:
122
- plugin_signing_credentials_path = _path(plugin_signing_credentials_path) if plugin_signing_credentials_path else self._select_file(TurtlesApp.PLUGIN_SIGNING_CREDENTIALS)
123
- with importlib.resources.path(lockss.turtles.resources, TurtlesApp.PLUGIN_SIGNING_CREDENTIALS_SCHEMA) as plugin_signing_credentials_schema_path:
124
- self._plugin_signing_credentials = _load_and_validate(plugin_signing_credentials_schema_path, plugin_signing_credentials_path)
99
+ plugin_signing_credentials_path = path(plugin_signing_credentials_path) if plugin_signing_credentials_path else self._select_file(TurtlesApp.PLUGIN_SIGNING_CREDENTIALS)
100
+ with IR.path(__resources__, TurtlesApp.PLUGIN_SIGNING_CREDENTIALS_SCHEMA) as plugin_signing_credentials_schema_path:
101
+ self._plugin_signing_credentials = load_and_validate(plugin_signing_credentials_schema_path, plugin_signing_credentials_path)
125
102
 
126
- # Returns plugin_id -> list of (registry_id, layer_id, dst_path, plugin)
127
- def release_plugin(self, plugin_ids, layer_ids, interactive=False):
103
+ def release_plugin(self, plugin_ids: List[str], layer_ids: List[str], interactive: bool=False) -> Dict[str, List[Tuple[str, str, Path, Plugin]]]:
128
104
  # ... plugin_id -> (set_id, jar_path, plugin)
129
105
  ret1 = self.build_plugin(plugin_ids)
130
106
  jar_paths = [jar_path for set_id, jar_path, plugin in ret1.values()]
@@ -134,54 +110,83 @@ class TurtlesApp(object):
134
110
  interactive=interactive)
135
111
  return {plugin_id: val for (jar_path, plugin_id), val in ret2.items()}
136
112
 
137
- def select_plugin_registry_catalog(self, preselected=None):
113
+ def select_plugin_registry_catalog(self, preselected: Optional[Union[Path, str]]=None) -> Path:
138
114
  return TurtlesApp._select_file(TurtlesApp.PLUGIN_REGISTRY_CATALOG, preselected)
139
115
 
140
- def select_plugin_set_catalog(self, preselected=None):
116
+ def select_plugin_set_catalog(self, preselected: Optional[Union[Path, str]]=None) -> Path:
141
117
  return TurtlesApp._select_file(TurtlesApp.PLUGIN_SET_CATALOG, preselected)
142
118
 
143
- def select_plugin_signing_credentials(self, preselected=None):
119
+ def select_plugin_signing_credentials(self, preselected: Optional[Union[Path, str]]=None) -> Path:
144
120
  return TurtlesApp._select_file(TurtlesApp.PLUGIN_SIGNING_CREDENTIALS, preselected)
145
121
 
146
- def set_password(self, pw):
147
- self._password = pw if callable(pw) else lambda x: pw
122
+ def set_password(self, pw: Union[Callable[[], str], str]) -> None:
123
+ self._password = pw if callable(pw) else lambda: pw
148
124
 
149
- # Returns (set_id, jar_path, plugin)
150
- def _build_one_plugin(self, plugin_id):
125
+ def _build_one_plugin(self, plugin_id: str) -> Tuple[str, Optional[Path], Optional[Plugin]]:
151
126
  for plugin_set in self._plugin_sets:
152
127
  if plugin_set.has_plugin(plugin_id):
153
- return (plugin_set.get_id(),
154
- *plugin_set.build_plugin(plugin_id,
155
- self._get_plugin_signing_keystore(),
156
- self._get_plugin_signing_alias(),
157
- self._get_plugin_signing_password()))
128
+ bp = plugin_set.build_plugin(plugin_id,
129
+ self._get_plugin_signing_keystore(),
130
+ self._get_plugin_signing_alias(),
131
+ self._get_plugin_signing_password())
132
+ return plugin_set.get_id(), bp[0] if bp else None, bp[1] if bp else None
158
133
  raise Exception(f'{plugin_id}: not found in any plugin set')
159
134
 
160
- # Returns list of (registry_id, layer_id, dst_path, plugin)
161
- def _deploy_one_plugin(self, src_jar, plugin_id, layer_ids, interactive=False):
135
+ def _deploy_one_plugin(self, src_jar: Path, plugin_id: str, layer_ids: List[str], interactive: bool=False) -> List[Tuple[str, str, Optional[Path], Optional[Plugin]]]:
162
136
  ret = list()
163
137
  for plugin_registry in self._plugin_registries:
164
138
  if plugin_registry.has_plugin(plugin_id):
165
139
  for layer_id in layer_ids:
166
140
  layer = plugin_registry.get_layer(layer_id)
167
141
  if layer is not None:
142
+ dp = layer.deploy_plugin(plugin_id,
143
+ src_jar,
144
+ interactive=interactive)
168
145
  ret.append((plugin_registry.get_id(),
169
146
  layer.get_id(),
170
- *layer.deploy_plugin(plugin_id,
171
- src_jar,
172
- interactive=interactive)))
147
+ dp[0] if dp else None,
148
+ dp[1] if dp else None))
173
149
  if len(ret) == 0:
174
150
  raise Exception(f'{src_jar}: {plugin_id} not declared in any plugin registry')
175
151
  return ret
176
152
 
177
- def _get_password(self):
153
+ def _get_password(self) -> Optional[str]:
178
154
  return self._password() if self._password else None
179
155
 
180
- def _get_plugin_signing_alias(self):
156
+ def _get_plugin_signing_alias(self) -> str:
181
157
  return self._plugin_signing_credentials['plugin-signing-alias']
182
158
 
183
- def _get_plugin_signing_keystore(self):
159
+ def _get_plugin_signing_keystore(self) -> str:
184
160
  return self._plugin_signing_credentials['plugin-signing-keystore']
185
161
 
186
- def _get_plugin_signing_password(self):
162
+ def _get_plugin_signing_password(self) -> str:
187
163
  return self._get_password()
164
+
165
+ @staticmethod
166
+ def default_plugin_registry_catalogs() -> List[Path]:
167
+ return TurtlesApp._default_files(TurtlesApp.PLUGIN_REGISTRY_CATALOG)
168
+
169
+ @staticmethod
170
+ def default_plugin_set_catalogs() -> List[Path]:
171
+ return TurtlesApp._default_files(TurtlesApp.PLUGIN_SET_CATALOG)
172
+
173
+ @staticmethod
174
+ def default_plugin_signing_credentials() -> List[Path]:
175
+ return TurtlesApp._default_files(TurtlesApp.PLUGIN_SIGNING_CREDENTIALS)
176
+
177
+ @staticmethod
178
+ def _default_files(file_str) -> List[Path]:
179
+ return [dir_path.joinpath(file_str) for dir_path in TurtlesApp.CONFIG_DIRS]
180
+
181
+ @staticmethod
182
+ def _select_file(file_str, preselected: Optional[Union[Path, str]]=None) -> Path:
183
+ if preselected:
184
+ preselected = path(preselected)
185
+ if not preselected.is_file():
186
+ raise FileNotFoundError(str(preselected))
187
+ return preselected
188
+ choices = TurtlesApp._default_files(file_str)
189
+ ret = next(filter(lambda f: f.is_file(), choices), None)
190
+ if ret is None:
191
+ raise FileNotFoundError(' or '.join(map(str, choices)))
192
+ return ret