lockss-debugpanel 0.8.2__tar.gz → 0.9.0.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,13 +2,19 @@
2
2
  Release Notes
3
3
  =============
4
4
 
5
+ -----
6
+ 0.9.0
7
+ -----
8
+
9
+ Released: NOT YET RELEASED
10
+
5
11
  -----
6
12
  0.8.2
7
13
  -----
8
14
 
9
15
  Released: 2026-02-03
10
16
 
11
- * Requires Python 3.9-3.13.
17
+ * Requires Python 3.9-3.13.
12
18
 
13
19
  -----
14
20
  0.8.1
@@ -20,10 +26,6 @@ Released: 2025-08-13
20
26
 
21
27
  * Fixed bug in the processing of ``--nodes`` and ``--auids`` options.
22
28
 
23
- * **Version 0.8.1-post1** (released: 2026-02-03)
24
-
25
- * Requires Python 3.9-3.13.
26
-
27
29
  -----
28
30
  0.8.0
29
31
  -----
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lockss-debugpanel
3
- Version: 0.8.2
4
- Summary: Library and command line tool to interact with the LOCKSS 1.x DebugPanel servlet
3
+ Version: 0.9.0.dev2
4
+ Summary: Command line tool and Python library to interact with the LOCKSS 1.x DebugPanel servlet
5
5
  License: BSD-3-Clause
6
6
  License-File: LICENSE
7
7
  Author: Thib Guicherd-Callin
8
8
  Author-email: thib@cs.stanford.edu
9
9
  Maintainer: Thib Guicherd-Callin
10
10
  Maintainer-email: thib@cs.stanford.edu
11
- Requires-Python: >=3.9,<3.14
11
+ Requires-Python: >=3.10,<4.0
12
12
  Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Environment :: Console
14
14
  Classifier: Framework :: Pydantic :: 2
@@ -19,10 +19,8 @@ Classifier: Programming Language :: Python
19
19
  Classifier: Topic :: Software Development :: Libraries
20
20
  Classifier: Topic :: System :: Archiving
21
21
  Classifier: Topic :: Utilities
22
- Requires-Dist: lockss-pybasic (>=0.1.0,<0.2.0)
23
- Requires-Dist: pydantic (>=2.11.0,<3.0.0)
24
- Requires-Dist: pydantic-argparse (>=0.10.0,<0.11.0)
25
- Requires-Dist: tabulate (>=0.9.0,<0.10.0)
22
+ Requires-Dist: click-extra (>=7.5.0,<7.6.0)
23
+ Requires-Dist: lockss-pybasic (==0.2.0-dev11)
26
24
  Project-URL: Documentation, https://docs.lockss.org/en/latest/software/debugpanel
27
25
  Project-URL: Repository, https://github.com/lockss/lockss-debugpanel
28
26
  Project-URL: changelog, https://github.com/lockss/lockss-debugpanel/blob/main/CHANGELOG.rst
@@ -33,8 +31,8 @@ Description-Content-Type: text/x-rst
33
31
  Debugpanel
34
32
  ==========
35
33
 
36
- .. |RELEASE| replace:: 0.8.2
37
- .. |RELEASE_DATE| replace:: 2026-02-03
34
+ .. |RELEASE| replace:: 0.9.0-dev2
35
+ .. |RELEASE_DATE| replace:: NOT YET RELEASED
38
36
  .. |DEBUGPANEL| replace:: **Debugpanel**
39
37
 
40
38
  .. image:: https://assets.lockss.org/images/logos/debugpanel/debugpanel_128x128.png
@@ -67,9 +65,14 @@ Quick Start::
67
65
  debugpanel reload-config -n lockss1.example.edu:8081
68
66
 
69
67
  # Crawl AUIDs from list.txt on lockss1.example.edu:8081 and lockss2.example.edu:8081
70
- # ...First alternative: each node gets a -n
68
+
69
+ # ...First alternative: each node gets a -n option
71
70
  debugpanel crawl -A list.txt -n lockss1.example.edu:8081 -n lockss2.example.edu:8081
72
71
 
73
- # ...Second alternative: each -n can have more than argument
72
+ # ...Second alternative: each -n option can have arguments
74
73
  debugpanel crawl -A list.txt -n lockss1.example.edu:8081 lockss2.example.edu:8081
75
74
 
75
+ # ...Third alternative: list lockss1.example.edu:8081 and lockss2.example.edu:8081 in nodes.txt
76
+ debugpanel crawl -A list.txt -N nodes.txt
77
+
78
+
@@ -2,8 +2,8 @@
2
2
  Debugpanel
3
3
  ==========
4
4
 
5
- .. |RELEASE| replace:: 0.8.2
6
- .. |RELEASE_DATE| replace:: 2026-02-03
5
+ .. |RELEASE| replace:: 0.9.0-dev2
6
+ .. |RELEASE_DATE| replace:: NOT YET RELEASED
7
7
  .. |DEBUGPANEL| replace:: **Debugpanel**
8
8
 
9
9
  .. image:: https://assets.lockss.org/images/logos/debugpanel/debugpanel_128x128.png
@@ -36,8 +36,13 @@ Quick Start::
36
36
  debugpanel reload-config -n lockss1.example.edu:8081
37
37
 
38
38
  # Crawl AUIDs from list.txt on lockss1.example.edu:8081 and lockss2.example.edu:8081
39
- # ...First alternative: each node gets a -n
39
+
40
+ # ...First alternative: each node gets a -n option
40
41
  debugpanel crawl -A list.txt -n lockss1.example.edu:8081 -n lockss2.example.edu:8081
41
42
 
42
- # ...Second alternative: each -n can have more than argument
43
+ # ...Second alternative: each -n option can have arguments
43
44
  debugpanel crawl -A list.txt -n lockss1.example.edu:8081 lockss2.example.edu:8081
45
+
46
+ # ...Third alternative: list lockss1.example.edu:8081 and lockss2.example.edu:8081 in nodes.txt
47
+ debugpanel crawl -A list.txt -N nodes.txt
48
+
@@ -28,11 +28,11 @@
28
28
 
29
29
  [project]
30
30
  name = "lockss-debugpanel"
31
- version = "0.8.2" # Always change in __init__.py, and at release time in README.rst and CHANGELOG.rst
32
- description = "Library and command line tool to interact with the LOCKSS 1.x DebugPanel servlet"
31
+ version = "0.9.0-dev2" # Always change in __init__.py, and at release time in README.rst and CHANGELOG.rst
32
+ description = "Command line tool and Python library to interact with the LOCKSS 1.x DebugPanel servlet"
33
33
  license = { text = "BSD-3-Clause" }
34
34
  readme = "README.rst"
35
- requires-python = ">=3.9,<3.14"
35
+ requires-python = ">=3.10,<4.0"
36
36
  authors = [
37
37
  { name = "Thib Guicherd-Callin", email = "thib@cs.stanford.edu" },
38
38
  ]
@@ -40,10 +40,8 @@ maintainers = [
40
40
  { name = "Thib Guicherd-Callin", email = "thib@cs.stanford.edu" }
41
41
  ]
42
42
  dependencies = [
43
- "lockss-pybasic (>=0.1.0,<0.2.0)",
44
- "pydantic (>=2.11.0,<3.0.0)",
45
- "pydantic-argparse (>=0.10.0,<0.11.0)",
46
- "tabulate (>=0.9.0,<0.10.0)"
43
+ "click-extra (>=7.5.0,<7.6.0)",
44
+ "lockss-pybasic (==0.2.0-dev11)",
47
45
  ]
48
46
  classifiers = [
49
47
  "Development Status :: 5 - Production/Stable",
@@ -1,38 +1,10 @@
1
1
  #!/usr/bin/env python3
2
2
 
3
- # Copyright (c) 2000-2025, 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
3
  """
32
4
  Library and command line tool to interact with the LOCKSS 1.x DebugPanel servlet.
33
5
  """
34
6
 
35
- __version__ = '0.8.2'
7
+ __version__ = '0.9.0-dev2'
36
8
 
37
9
  __copyright__ = '''
38
10
  Copyright (c) 2000-2025, Board of Trustees of Leland Stanford Jr. University
@@ -69,7 +41,7 @@ POSSIBILITY OF SUCH DAMAGE.
69
41
 
70
42
  from base64 import b64encode
71
43
  from urllib.request import Request, urlopen
72
- from typing import Any, Dict
44
+ from typing import Any
73
45
 
74
46
 
75
47
  type RequestUrlOpenT = Any
@@ -303,7 +275,7 @@ def _auid_action(node: Node, auid: str, action: str, **kwargs) -> RequestUrlOpen
303
275
  ``Force Deep Crawl``.
304
276
  :type action: str
305
277
  :param kwargs: Key-value pairs of additional query string arguments.
306
- :type kwargs: Dict[str, Any]
278
+ :type kwargs: dict[str, Any]
307
279
  :return: The result of calling `urllib.request.urlopen`` on an appropriate
308
280
  URL.
309
281
  :rtype: RequestUrlOpenT
@@ -326,7 +298,7 @@ def _make_request(node: Node, query: str, **kwargs) -> Request:
326
298
  :type query: str
327
299
  :param kwargs: Key-value pairs of additional query string arguments, e.g.
328
300
  ``(..., depth=99)`` to add ``"&depth=99"``.
329
- :type kwargs: Dict[str, Any]
301
+ :type kwargs: dict[str, Any]
330
302
  :return: An authenticated ``Request`` instance (before
331
303
  ``urllib.request.urlopen`` is called).
332
304
  :rtype: Request
@@ -350,7 +322,7 @@ def _node_action(node: Node, action: str, **kwargs) -> RequestUrlOpenT:
350
322
  :type action: str
351
323
  :param kwargs: Key-value pairs of additional query string arguments, e.g.
352
324
  ``(..., depth=99)`` to add ``"&depth=99"``.
353
- :type kwargs: Dict[str, Any]
325
+ :type kwargs: dict[str, Any]
354
326
  :return: The result of calling `urllib.request.urlopen`` on an appropriate
355
327
  URL.
356
328
  :rtype: RequestUrlOpenT
@@ -0,0 +1,400 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright (c) 2000-2025, 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
+ """
32
+ Command line tool to interact with the LOCKSS 1.x DebugPanel servlet.
33
+ """
34
+
35
+ from collections.abc import Callable
36
+ from concurrent.futures import Executor, Future, ProcessPoolExecutor, ThreadPoolExecutor, as_completed
37
+ from enum import Enum
38
+ from itertools import chain
39
+ from pathlib import Path
40
+ from typing import Any, Optional
41
+
42
+ from click_extra import ChoiceSource, EnumChoice, ExtraContext, HelpExtraFormatter, IntRange, Style, color_option, group, option, option_group, pass_context, password_option, progressbar, show_params_option, table_format_option
43
+ from click_extra.colorize import default_theme
44
+ from cloup.constraints import mutually_exclusive
45
+
46
+ from lockss.pybasic.cliutil import click_path
47
+ from lockss.pybasic.errorutil import InternalError
48
+ from lockss.pybasic.fileutil import file_lines, path
49
+ from . import Node, RequestUrlOpenT, check_substance, crawl, crawl_plugins, deep_crawl, disable_indexing, poll, reload_config, reindex_metadata, validate_files, DEFAULT_DEPTH, __copyright__, __license__, __version__
50
+
51
+
52
+ class _JobPoolType(Enum):
53
+ """
54
+ An enum of job pool types.
55
+
56
+ See also ``_DEFAULT_JOB_POOL_TYPE``.
57
+ """
58
+ THREAD_POOL = 'thread-pool'
59
+ PROCESS_POOL = 'process-pool'
60
+
61
+ def __str__(self):
62
+ return self.value
63
+
64
+
65
+ _DEFAULT_JOB_POOL_TYPE: _JobPoolType = _JobPoolType.THREAD_POOL
66
+
67
+
68
+ class _DebugPanelCli(object):
69
+
70
+ def __init__(self):
71
+ super().__init__()
72
+ self._auids: Optional[list[str]] = None
73
+ self._auth: Optional[tuple[str, str]] = None
74
+ self._ctx: Optional[ExtraContext] = None
75
+ self._executor: Optional[Executor] = None
76
+ self._nodes: Optional[list[str]] = None
77
+
78
+ def _do_auid_command(self,
79
+ node_auid_func: Callable[[Node, str], RequestUrlOpenT],
80
+ **kwargs: dict[str, Any]) -> None:
81
+ """
82
+ Performs one AUID-centric command.
83
+
84
+ :param node_auid_func: A function that applies to a ``Node`` and an AUID
85
+ and returns what ``urllib.request.urlopen``
86
+ returns.
87
+ :type node_auid_func: ``RequestUrlOpenT``
88
+ :param kwargs: Keyword arguments (needed for the ``depth`` command).
89
+ :type kwargs: Dict[str, Any]
90
+ """
91
+ node_objects = [Node(node, *self._auth) for node in self._nodes]
92
+ futures: dict[Future, tuple[str, str]] = {self._executor.submit(node_auid_func, node_object, auid, **kwargs): (node, auid) for auid in self._auids for node, node_object in zip(self._nodes, node_objects)}
93
+ results: dict[tuple[str, str], Any] = {}
94
+ with progressbar(as_completed(futures), length=len(futures), label='Progress') as bar:
95
+ for future in bar:
96
+ with future.result() as resp:
97
+ node_auid = futures[future]
98
+ try:
99
+ status: int = resp.status
100
+ reason: str = resp.reason
101
+ results[node_auid] = 'Requested' if status == 200 else reason
102
+ except Exception as exc:
103
+ results[node_auid] = exc
104
+ self._ctx.print_table([[auid, *[results[(node, auid)] for node in self._nodes]] for auid in self._auids],
105
+ ['AUID', *self._nodes])
106
+
107
+ def _do_node_command(self,
108
+ node_func: Callable[[Node], RequestUrlOpenT],
109
+ **kwargs: dict[str, Any]) -> None:
110
+ """
111
+ Performs one node-centric command.
112
+
113
+ :param node_func: A function that applies to a ``Node`` and returns
114
+ what ``urllib.request.urlopen`` returns.
115
+ :type node_auid_func: ``RequestUrlOpenT``
116
+ :param kwargs: Keyword arguments (not currently needed by any command).
117
+ :type kwargs: Dict[str, Any]
118
+ """
119
+ node_objects = [Node(node, *self._auth) for node in self._nodes]
120
+ futures: dict[Future, str] = {self._executor.submit(node_func, node_object, **kwargs): node for node, node_object in zip(self._nodes, node_objects)}
121
+ results: dict[str, Any] = {}
122
+ with progressbar(as_completed(futures), length=len(futures), label='Progress') as bar:
123
+ for future in bar:
124
+ with future.result() as resp:
125
+ node = futures[future]
126
+ try:
127
+ status: int = resp.status
128
+ reason: str = resp.reason
129
+ results[node] = 'Requested' if status == 200 else reason
130
+ except Exception as exc:
131
+ results[node] = exc
132
+ self._ctx.print_table([[node, results[node]] for node in self._nodes],
133
+ ['Node', 'Result'])
134
+
135
+ def _initialize_auid_operation(self,
136
+ cli_ctx: ExtraContext,
137
+ cli_node: tuple[str, ...],
138
+ cli_nodes: tuple[Path, ...],
139
+ cli_username: str,
140
+ cli_password: str,
141
+ cli_auid: tuple[str, ...],
142
+ cli_auids: tuple[Path, ...],
143
+ cli_pool_size: Optional[int],
144
+ cli_pool_type: _JobPoolType,
145
+ cli_process_pool: bool,
146
+ cli_thread_pool: bool) -> None:
147
+ self._ctx = self._ctx or cli_ctx
148
+ self._initialize_node_operation(cli_ctx,
149
+ cli_node,
150
+ cli_nodes,
151
+ cli_username,
152
+ cli_password,
153
+ cli_pool_size,
154
+ cli_pool_type,
155
+ cli_process_pool,
156
+ cli_thread_pool)
157
+ self._auids = [*cli_auid, *chain.from_iterable(file_lines(file_path) for file_path in cli_auids)]
158
+ if len(self._auids) == 0:
159
+ raise ValueError('The list of AUIDs to process is empty')
160
+
161
+ def _initialize_node_operation(self,
162
+ cli_ctx: ExtraContext,
163
+ cli_node: tuple[str, ...],
164
+ cli_nodes: tuple[Path, ...],
165
+ cli_username: str,
166
+ cli_password: str,
167
+ cli_pool_size: Optional[int],
168
+ cli_pool_type: _JobPoolType,
169
+ cli_process_pool: bool,
170
+ cli_thread_pool: bool) -> None:
171
+ self._ctx = self._ctx or cli_ctx
172
+ self._nodes = [*cli_node, *chain.from_iterable(file_lines(file_path) for file_path in cli_nodes)]
173
+ if len(self._nodes) == 0:
174
+ raise ValueError('The list of nodes to process is empty')
175
+ self._auth = (cli_username, cli_password)
176
+ if cli_process_pool:
177
+ cli_pool_type = _JobPoolType.PROCESS_POOL
178
+ elif cli_thread_pool:
179
+ cli_pool_type = _JobPoolType.THREAD_POOL
180
+ match cli_pool_type:
181
+ case _JobPoolType.PROCESS_POOL:
182
+ self._executor = ProcessPoolExecutor(max_workers=cli_pool_size)
183
+ case _JobPoolType.THREAD_POOL:
184
+ self._executor = ThreadPoolExecutor(max_workers=cli_pool_size)
185
+ case _:
186
+ raise InternalError() from ValueError(cli_pool_type)
187
+
188
+
189
+ _node_options = option_group(
190
+ 'Node options',
191
+ option('--node', '-n', metavar='NODE', multiple=True, help='Add NODE to the list of nodes to process.'),
192
+ option('--nodes', '-N', metavar='FILE', type=click_path('ferz'), multiple=True, help='Add the nodes in FILE to the list of nodes to process.'),
193
+ option('--username', '-u', metavar='USER', show_default='interactive prompt', help='Set the UI username to USER.', prompt='UI username'),
194
+ password_option('--password', '-p', metavar='PASS', show_default='interactive prompt', help='Set the UI password to PASS.', prompt='UI password', confirmation_prompt=False)
195
+ )
196
+
197
+
198
+ _auid_options = option_group(
199
+ 'AUID options',
200
+ option('--auid', '-a', metavar='AUID', multiple=True, help='Add AUID to the list of AUIDs to process.'),
201
+ option('--auids', '-A', metavar='FILE', type=click_path('ferz'), multiple=True, help='Add the AUIDs in FILE to the list of AUIDs to process.')
202
+ )
203
+
204
+
205
+ _pool_options = option_group(
206
+ 'Job pool options',
207
+ option('--pool-size', metavar='SIZE', type=Optional[IntRange(1, None)], default=None, help='Set the job pool size to SIZE.', show_default='CPU-dependent'),
208
+ mutually_exclusive(
209
+ option('--pool-type', type=EnumChoice(choices=_JobPoolType, choice_source=ChoiceSource.VALUE), show_choices=True, default=_DEFAULT_JOB_POOL_TYPE, help=f'Set the job pool type to the given type.'),
210
+ option('--process-pool', is_flag=True, deprecated='Use --pool-type=process-pool instead.'),
211
+ option('--thread-pool', is_flag=True, deprecated='Use --pool-type=thread-pool instead.')
212
+ )
213
+ )
214
+
215
+
216
+ _table_format_option = table_format_option(help='Set the rendering of tables to the given style.')
217
+
218
+ @group('debugpanel', params=None,
219
+ context_settings=ExtraContext.settings(
220
+ formatter_settings=HelpExtraFormatter.settings(
221
+ theme=default_theme.with_(
222
+ invoked_command=Style(bold=True)
223
+ )
224
+ )
225
+ )
226
+ )
227
+ @color_option
228
+ @show_params_option
229
+ @pass_context
230
+ def _debugpanel(ctx: ExtraContext, **kwargs):
231
+ ctx.obj = _DebugPanelCli()
232
+
233
+
234
+ @_debugpanel.command('check-substance', aliases=['cs'], help='Cause nodes to check the substance of AUs.')
235
+ @_node_options
236
+ @_auid_options
237
+ @_pool_options
238
+ @_table_format_option
239
+ @pass_context
240
+ def _check_substance(ctx: ExtraContext, **kwargs) -> None:
241
+ cli: _DebugPanelCli = ctx.obj
242
+ args = [kwargs.get(k) for k in ['node', 'nodes', 'username', 'password', 'auid', 'auids', 'pool_size', 'pool_type', 'process_pool', 'thread_pool']]
243
+ try:
244
+ cli._initialize_auid_operation(ctx, *args)
245
+ except ValueError as ve:
246
+ ctx.fail(str(ve))
247
+ cli._do_auid_command(check_substance)
248
+
249
+
250
+ @_debugpanel.command('copyright', help='Show the copyright then exit.')
251
+ def _copyright() -> None:
252
+ print(__copyright__)
253
+
254
+
255
+ @_debugpanel.command('crawl', aliases=['cr'], help='Cause nodes to crawl AUs.')
256
+ @_node_options
257
+ @_auid_options
258
+ @_pool_options
259
+ @table_format_option
260
+ @pass_context
261
+ def _crawl(ctx: ExtraContext, **kwargs) -> None:
262
+ cli: _DebugPanelCli = ctx.obj
263
+ args = [kwargs.get(k) for k in ['node', 'nodes', 'username', 'password', 'auid', 'auids', 'pool_size', 'pool_type', 'process_pool', 'thread_pool']]
264
+ try:
265
+ cli._initialize_auid_operation(ctx, *args)
266
+ except ValueError as ve:
267
+ ctx.fail(str(ve))
268
+ cli._do_auid_command(crawl)
269
+
270
+
271
+ @_debugpanel.command('crawl-plugins', aliases=['cp'], help='Cause nodes to crawl plugins.')
272
+ @_node_options
273
+ @_pool_options
274
+ @table_format_option
275
+ @pass_context
276
+ def _crawl_plugins(ctx: ExtraContext, **kwargs) -> None:
277
+ cli: _DebugPanelCli = ctx.obj
278
+ args = [kwargs.get(k) for k in ['node', 'nodes', 'username', 'password', 'pool_size', 'pool_type', 'process_pool', 'thread_pool']]
279
+ try:
280
+ cli._initialize_node_operation(ctx, *args)
281
+ except ValueError as ve:
282
+ ctx.fail(str(ve))
283
+ cli._do_node_command(crawl_plugins)
284
+
285
+
286
+ @_debugpanel.command('deep-crawl', aliases=['dc'], help='Cause nodes to deep-crawl AUs.')
287
+ @_node_options
288
+ @_auid_options
289
+ @option_group(
290
+ 'Depth options',
291
+ option('--depth', '-d', metavar='DEPTH', type=IntRange(1, None), default=DEFAULT_DEPTH, help='Set the crawl depth to DEPTH.')
292
+ )
293
+ @_pool_options
294
+ @table_format_option
295
+ @pass_context
296
+ def _deep_crawl(ctx: ExtraContext, **kwargs) -> None:
297
+ cli: _DebugPanelCli = ctx.obj
298
+ args = [kwargs.get(k) for k in ['node', 'nodes', 'username', 'password', 'auid', 'auids', 'pool_size', 'pool_type', 'process_pool', 'thread_pool']]
299
+ try:
300
+ cli._initialize_auid_operation(ctx, *args)
301
+ except ValueError as ve:
302
+ ctx.fail(str(ve))
303
+ cli._do_auid_command(deep_crawl, depth=kwargs.get('depth'))
304
+
305
+
306
+ @_debugpanel.command('disable-indexing', aliases=['di'], help='Cause nodes to disable metadata indexing for AUs.')
307
+ @_node_options
308
+ @_auid_options
309
+ @_pool_options
310
+ @table_format_option
311
+ @pass_context
312
+ def _disable_indexing(ctx: ExtraContext, **kwargs) -> None:
313
+ cli: _DebugPanelCli = ctx.obj
314
+ args = [kwargs.get(k) for k in ['node', 'nodes', 'username', 'password', 'auid', 'auids', 'pool_size', 'pool_type', 'process_pool', 'thread_pool']]
315
+ try:
316
+ cli._initialize_auid_operation(ctx, *args)
317
+ except ValueError as ve:
318
+ ctx.fail(str(ve))
319
+ cli._do_auid_command(disable_indexing)
320
+
321
+
322
+ @_debugpanel.command('license', help='Show the software license then exit.')
323
+ def license() -> None:
324
+ print(__license__)
325
+
326
+
327
+ @_debugpanel.command('poll', aliases=['po'], help='Cause nodes to poll AUs.')
328
+ @_node_options
329
+ @_auid_options
330
+ @_pool_options
331
+ @table_format_option
332
+ @pass_context
333
+ def _poll(ctx: ExtraContext, **kwargs) -> None:
334
+ cli: _DebugPanelCli = ctx.obj
335
+ args = [kwargs.get(k) for k in ['node', 'nodes', 'username', 'password', 'auid', 'auids', 'pool_size', 'pool_type', 'process_pool', 'thread_pool']]
336
+ try:
337
+ cli._initialize_auid_operation(ctx, *args)
338
+ except ValueError as ve:
339
+ ctx.fail(str(ve))
340
+ cli._do_auid_command(poll)
341
+
342
+
343
+ @_debugpanel.command('reindex-metadata', aliases=['ri'], help='Cause nodes to reindex the metadata of AUs.')
344
+ @_node_options
345
+ @_auid_options
346
+ @_pool_options
347
+ @table_format_option
348
+ @pass_context
349
+ def _reindex_metadata(ctx: ExtraContext, **kwargs) -> None:
350
+ cli: _DebugPanelCli = ctx.obj
351
+ args = [kwargs.get(k) for k in ['node', 'nodes', 'username', 'password', 'auid', 'auids', 'pool_size', 'pool_type', 'process_pool', 'thread_pool']]
352
+ try:
353
+ cli._initialize_auid_operation(ctx, *args)
354
+ except ValueError as ve:
355
+ ctx.fail(str(ve))
356
+ cli._do_auid_command(reindex_metadata)
357
+
358
+
359
+ @_debugpanel.command('reload-config', aliases=['rc'], help='Cause nodes to reload their configuration.')
360
+ @_node_options
361
+ @_pool_options
362
+ @table_format_option
363
+ @pass_context
364
+ def _reload_config(ctx: ExtraContext, **kwargs) -> None:
365
+ cli: _DebugPanelCli = ctx.obj
366
+ args = [kwargs.get(k) for k in ['node', 'nodes', 'username', 'password', 'pool_size', 'pool_type', 'process_pool', 'thread_pool']]
367
+ try:
368
+ cli._initialize_node_operation(ctx, *args)
369
+ except ValueError as ve:
370
+ ctx.fail(str(ve))
371
+ cli._do_node_command(reload_config)
372
+
373
+
374
+ @_debugpanel.command('validate-files', aliases=['vf'], help='Cause nodes to validate the files of AUs.')
375
+ @_node_options
376
+ @_auid_options
377
+ @_pool_options
378
+ @table_format_option
379
+ @pass_context
380
+ def _validate_files(ctx: ExtraContext, **kwargs) -> None:
381
+ cli: _DebugPanelCli = ctx.obj
382
+ args = [kwargs.get(k) for k in ['node', 'nodes', 'username', 'password', 'auid', 'auids', 'pool_size', 'pool_type', 'process_pool', 'thread_pool']]
383
+ try:
384
+ cli._initialize_auid_operation(ctx, *args)
385
+ except ValueError as ve:
386
+ ctx.fail(str(ve))
387
+ cli._do_auid_command(validate_files)
388
+
389
+
390
+ @_debugpanel.command('version', help='Show the version number then exit.')
391
+ def version() -> None:
392
+ print(__version__)
393
+
394
+
395
+ def main() -> None:
396
+ _debugpanel()
397
+
398
+
399
+ if __name__ == '__main__':
400
+ main()
@@ -1,379 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- # Copyright (c) 2000-2025, 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
- """
32
- Command line tool to interact with the LOCKSS 1.x DebugPanel servlet.
33
- """
34
-
35
- from collections.abc import Callable
36
- from concurrent.futures import Executor, Future, ProcessPoolExecutor, ThreadPoolExecutor, as_completed
37
- from enum import Enum
38
- from getpass import getpass
39
- from itertools import chain
40
- from pathlib import Path
41
- from typing import Any, Dict, List, Optional, Tuple
42
-
43
- from pydantic.v1 import BaseModel, Field, FilePath, root_validator, validator
44
- from pydantic.v1.types import PositiveInt
45
- from tabulate import tabulate
46
-
47
- from lockss.pybasic.cliutil import BaseCli, StringCommand, at_most_one_from_enum, get_from_enum, COPYRIGHT_DESCRIPTION, LICENSE_DESCRIPTION, VERSION_DESCRIPTION
48
- from lockss.pybasic.errorutil import InternalError
49
- from lockss.pybasic.fileutil import file_lines, path
50
- from lockss.pybasic.outpututil import OutputFormatOptions
51
- from . import Node, RequestUrlOpenT, check_substance, crawl, crawl_plugins, deep_crawl, disable_indexing, poll, reload_config, reindex_metadata, validate_files, DEFAULT_DEPTH, __copyright__, __license__, __version__
52
-
53
-
54
- class JobPool(Enum):
55
- """
56
- An enum of job pool types.
57
-
58
- See also ``DEFAULT_POOL_TYPE``.
59
- """
60
- thread_pool = 'thread-pool'
61
- process_pool = 'process-pool'
62
-
63
- @staticmethod
64
- def from_option(name: str) -> str:
65
- """
66
- Given an option name with hyphens, return the enum constant name with
67
- underscores.
68
-
69
- :param name: An option name with hyphens.
70
- :type name: str
71
- :return: The corresponding enum constant name with underscores.
72
- :rtype: str
73
- """
74
- return JobPool(name.replace('-', '_'))
75
-
76
-
77
- DEFAULT_POOL_SIZE: Optional[int] = None
78
- DEFAULT_POOL_TYPE: JobPool = JobPool.thread_pool
79
-
80
-
81
- class NodesOptions(BaseModel):
82
- """
83
- The --node/-n, --nodes/-N, --password/-p and --username/-u options.
84
- """
85
- node: Optional[List[str]] = Field([], aliases=['-n'], description='(nodes) add one or more nodes to the set of nodes to process')
86
- nodes: Optional[List[FilePath]] = Field([], aliases=['-N'], description='(nodes) add the nodes listed in one or more files to the set of nodes to process')
87
- password: Optional[str] = Field(aliases=['-p'], description='(nodes) UI password; interactive prompt if not specified')
88
- username: Optional[str] = Field(aliases=['-u'], description='(nodes) UI username; interactive prompt if not unspecified')
89
-
90
- @validator('nodes', each_item=True, pre=True)
91
- def _expand_each_nodes_path(cls, v: Path):
92
- return path(v)
93
-
94
- def get_nodes(self):
95
- ret = [*self.node, *chain.from_iterable(file_lines(file_path) for file_path in self.nodes)]
96
- if len(ret) == 0:
97
- raise RuntimeError('empty list of nodes')
98
- return ret
99
-
100
-
101
- class AuidsOptions(BaseModel):
102
- """
103
- The --auid/-a and --auids/-A options.
104
- """
105
- auid: Optional[List[str]] = Field([], aliases=['-a'], description='(AUIDs) add one or more AUIDs to the set of AUIDs to process')
106
- auids: Optional[List[FilePath]] = Field([], aliases=['-A'], description='(AUIDs) add the AUIDs listed in one or more files to the set of AUIDs to process')
107
-
108
- @validator('auids', each_item=True, pre=True)
109
- def _expand_each_auids_path(cls, v: Path):
110
- return path(v)
111
-
112
- def get_auids(self):
113
- ret = [*self.auid, *chain.from_iterable(file_lines(file_path) for file_path in self.auids)]
114
- if len(ret) == 0:
115
- raise RuntimeError('empty list of AUIDs')
116
- return ret
117
-
118
-
119
- class DepthOptions(BaseModel):
120
- """
121
- The --depth/-d option.
122
- """
123
- depth: Optional[int] = Field(DEFAULT_DEPTH, aliases=['-d'], description='(deep crawl) set crawl depth')
124
-
125
-
126
- class JobPoolOptions(BaseModel):
127
- """
128
- The --pool-size, --process-pool and --thread-pool options.
129
- """
130
- pool_size: Optional[PositiveInt] = Field(description='(job pool) set the job pool size')
131
- process_pool: Optional[bool] = Field(False, description='(job pool) use a process pool', enum=JobPool)
132
- thread_pool: Optional[bool] = Field(False, description='(job pool) use a thread pool', enum=JobPool)
133
-
134
- @root_validator
135
- def _at_most_one_pool_type(cls, values):
136
- return at_most_one_from_enum(cls, values, JobPool)
137
-
138
- def get_pool_size(self) -> Optional[int]:
139
- return self.pool_size if hasattr(self, 'pool_size') else DEFAULT_POOL_SIZE
140
-
141
- def get_pool_type(self) -> JobPool:
142
- return get_from_enum(self, JobPool, DEFAULT_POOL_TYPE)
143
-
144
-
145
- class NodeCommand(OutputFormatOptions, JobPoolOptions, NodesOptions):
146
- """
147
- A pydantic-argparse command for node commands.
148
- """
149
- pass
150
-
151
- class AuidCommand(NodeCommand, OutputFormatOptions, JobPoolOptions, AuidsOptions, NodesOptions):
152
- """
153
- A pydantic-argparse command for AUID commands except deep-crawl.
154
- """
155
- pass
156
-
157
- class DeepCrawlCommand(AuidCommand, OutputFormatOptions, JobPoolOptions, DepthOptions, AuidsOptions, NodesOptions):
158
- """
159
- A pydantic-argparse command for deep-crawl.
160
- """
161
- pass
162
-
163
-
164
- class DebugPanelCommand(BaseModel):
165
- """
166
- The pydantic-argparse model for the top-level debugpanel command.
167
- """
168
- check_substance: Optional[AuidCommand] = Field(description='cause nodes to check the substance of AUs', alias='check-substance')
169
- copyright: Optional[StringCommand.type(__copyright__)] = Field(description=COPYRIGHT_DESCRIPTION)
170
- cp: Optional[NodeCommand] = Field(description='synonym for: crawl-plugins')
171
- cr: Optional[AuidCommand] = Field(description='synonym for: crawl')
172
- crawl: Optional[AuidCommand] = Field(description='cause nodes to crawl AUs')
173
- crawl_plugins: Optional[NodeCommand] = Field(description='cause nodes to crawl plugins', alias='crawl-plugins')
174
- cs: Optional[AuidCommand] = Field(description='synonym for: check-substance')
175
- dc: Optional[DeepCrawlCommand] = Field(description='synonym for: deep-crawl')
176
- deep_crawl: Optional[DeepCrawlCommand] = Field(description='cause nodes to deeply crawl AUs', alias='deep-crawl')
177
- di: Optional[AuidCommand] = Field(description='synonym for: disable-indexing')
178
- disable_indexing: Optional[AuidCommand] = Field(description='cause nodes to disable metadata indexing for AUs', alias='disable-indexing')
179
- license: Optional[StringCommand.type(__license__)] = Field(description=LICENSE_DESCRIPTION)
180
- po: Optional[AuidCommand] = Field(description='synonym for: poll')
181
- poll: Optional[AuidCommand] = Field(description='cause nodes to poll AUs')
182
- rc: Optional[NodeCommand] = Field(description='synonym for: reload-config')
183
- reindex_metadata: Optional[AuidCommand] = Field(description='cause nodes to reindex the metadata of AUs', alias='reindex-metadata')
184
- reload_config: Optional[NodeCommand] = Field(description='cause nodes to reload their configuration', alias='reload-config')
185
- ri: Optional[AuidCommand] = Field(description='synonym for: reindex-metadata')
186
- validate_files: Optional[AuidCommand] = Field(description='cause nodes to validate the files of AUs', alias='validate-files')
187
- version: Optional[StringCommand.type(__version__)] = Field(description=VERSION_DESCRIPTION)
188
- vf: Optional[AuidCommand] = Field(description='synonym for: validate-files')
189
-
190
-
191
- class DebugPanelCli(BaseCli[DebugPanelCommand]):
192
- """
193
- The debugpanel command line tool.
194
- """
195
-
196
- def __init__(self):
197
- """
198
- Constructs a new ``DebugPanelCli`` instance.
199
- """
200
- super().__init__(model=DebugPanelCommand,
201
- prog='debugpanel',
202
- description='Tool to interact with the LOCKSS 1.x DebugPanel servlet')
203
- self._auids: Optional[List[str]] = None
204
- self._auth: Optional[Any] = None
205
- self._executor: Optional[Executor] = None
206
- self._nodes: Optional[List[str]] = None
207
-
208
- def _check_substance(self, auid_command: AuidCommand) -> None:
209
- self._do_auid_command(auid_command, check_substance)
210
-
211
- def _copyright(self, string_command: StringCommand) -> None:
212
- self._do_string_command(string_command)
213
-
214
- def _cp(self, node_command: NodeCommand) -> None:
215
- self._crawl_plugins(node_command)
216
-
217
- def _cr(self, auid_command: AuidCommand) -> None:
218
- self._crawl(auid_command)
219
-
220
- def _crawl(self, auid_command: AuidCommand) -> None:
221
- self._do_auid_command(auid_command, crawl)
222
-
223
- def _crawl_plugins(self, node_command: NodeCommand) -> None:
224
- self._do_node_command(node_command, crawl_plugins)
225
-
226
- def _cs(self, auid_command: AuidCommand) -> None:
227
- self._check_substance(auid_command)
228
-
229
- def _dc(self, deep_crawl_command: DeepCrawlCommand) -> None:
230
- self._deep_crawl(deep_crawl_command)
231
-
232
- def _deep_crawl(self, deep_crawl_command: DeepCrawlCommand) -> None:
233
- self._do_auid_command(deep_crawl_command, deep_crawl, depth=deep_crawl_command.depth)
234
-
235
- def _di(self, auid_command: AuidCommand) -> None:
236
- self._disable_indexing(auid_command)
237
-
238
- def _disable_indexing(self, auid_command: AuidCommand) -> None:
239
- self._do_auid_command(auid_command, disable_indexing)
240
-
241
- def _do_auid_command(self, auid_command: AuidCommand, node_auid_func: Callable[[Node, str], RequestUrlOpenT], **kwargs: Dict[str, Any]) -> None:
242
- """
243
- Performs one AUID-centric command.
244
-
245
- :param auid_command: An ``AuidCommand`` model.
246
- :type auid_command: AuidCommand
247
- :param node_auid_func: A function that applies to a ``Node`` and an AUID
248
- and returns what ``urllib.request.urlopen``
249
- returns.
250
- :type node_auid_func: ``RequestUrlOpenT``
251
- :param kwargs: Keyword arguments (needed for the ``depth`` command).
252
- :type kwargs: Dict[str, Any]
253
- """
254
- self._initialize_auth(auid_command)
255
- self._initialize_executor(auid_command)
256
- self._nodes = auid_command.get_nodes()
257
- self._auids = auid_command.get_auids()
258
- node_objects = [Node(node, *self._auth) for node in self._nodes]
259
- futures: Dict[Future, Tuple[str, str]] = {self._executor.submit(node_auid_func, node_object, auid, **kwargs): (node, auid) for auid in self._auids for node, node_object in zip(self._nodes, node_objects)}
260
- results: Dict[Tuple[str, str], Any] = {}
261
- for future in as_completed(futures):
262
- node_auid = futures[future]
263
- try:
264
- resp: RequestUrlOpenT = future.result()
265
- status: int = resp.status
266
- reason: str = resp.reason
267
- results[node_auid] = 'Requested' if status == 200 else reason
268
- except Exception as exc:
269
- results[node_auid] = exc
270
- print(tabulate([[auid, *[results[(node, auid)] for node in self._nodes]] for auid in self._auids],
271
- headers=['AUID', *self._nodes],
272
- tablefmt=auid_command.output_format))
273
-
274
- def _do_node_command(self, node_command: NodeCommand, node_func: Callable[[Node], RequestUrlOpenT], **kwargs: Dict[str, Any]) -> None:
275
- """
276
- Performs one node-centric command.
277
-
278
- :param node_command: A ``NodeCommand`` model.
279
- :type auid_command: NodeCommand
280
- :param node_func: A function that applies to a ``Node`` and returns
281
- what ``urllib.request.urlopen`` returns.
282
- :type node_auid_func: ``RequestUrlOpenT``
283
- :param kwargs: Keyword arguments (not currently needed by any command).
284
- :type kwargs: Dict[str, Any]
285
- """
286
- self._initialize_auth(node_command)
287
- self._initialize_executor(node_command)
288
- self._nodes = node_command.get_nodes()
289
- node_objects = [Node(node, *self._auth) for node in self._nodes]
290
- futures: Dict[Future, str] = {self._executor.submit(node_func, node_object, **kwargs): node for node, node_object in zip(self._nodes, node_objects)}
291
- results: Dict[str, Any] = {}
292
- for future in as_completed(futures):
293
- node = futures[future]
294
- try:
295
- resp: RequestUrlOpenT = future.result()
296
- status: int = resp.status
297
- reason: str = resp.reason
298
- results[node] = 'Requested' if status == 200 else reason
299
- except Exception as exc:
300
- results[node] = exc
301
- print(tabulate([[node, results[node]] for node in self._nodes],
302
- headers=['Node', 'Result'],
303
- tablefmt=node_command.output_format))
304
-
305
- def _do_string_command(self, string_command: StringCommand) -> None:
306
- """
307
- Performs one string command.
308
-
309
- :param string_command: A ``StringCommand`` model.
310
- :type auid_command: StringCommand
311
- """
312
- string_command()
313
-
314
- def _initialize_auth(self, nodes_options: NodesOptions) -> None:
315
- """
316
- Computes the ``self._auth`` value, possibly after asking for interactive
317
- input.
318
-
319
- :param nodes_options: A ``NodesOptions`` model.
320
- :type node_options: ``NodesOptions``
321
- """
322
- _u = nodes_options.username or input('UI username: ')
323
- _p = nodes_options.password or getpass('UI password: ')
324
- self._auth = (_u, _p)
325
-
326
- def _initialize_executor(self, job_pool_options: JobPoolOptions) -> None:
327
- """
328
- Initializes the ``Executor``.
329
-
330
- :param job_pool_options: A ``JobPoolOptions`` model.
331
- :type job_pool_options: ``JobPoolOptions``.
332
- """
333
- if job_pool_options.get_pool_type() == JobPool.thread_pool:
334
- self._executor = ThreadPoolExecutor(max_workers=job_pool_options.get_pool_size())
335
- elif job_pool_options.get_pool_type() == JobPool.process_pool:
336
- self._executor = ProcessPoolExecutor(max_workers=job_pool_options.get_pool_size())
337
- else:
338
- raise InternalError()
339
-
340
- def _license(self, string_command: StringCommand) -> None:
341
- self._do_string_command(string_command)
342
-
343
- def _po(self, auid_command: AuidCommand) -> None:
344
- self._poll(auid_command)
345
-
346
- def _poll(self, auid_command: AuidCommand) -> None:
347
- self._do_auid_command(auid_command, poll)
348
-
349
- def _rc(self, node_command: NodeCommand):
350
- self._reload_config(node_command)
351
-
352
- def _ri(self, auid_command: AuidCommand) -> None:
353
- self._reindex_metadata(auid_command)
354
-
355
- def _reindex_metadata(self, auid_command: AuidCommand) -> None:
356
- self._do_auid_command(auid_command, reindex_metadata)
357
-
358
- def _reload_config(self, node_command: NodeCommand):
359
- self._do_node_command(node_command, reload_config)
360
-
361
- def _validate_files(self, auid_command: AuidCommand) -> None:
362
- self._do_auid_command(auid_command, validate_files)
363
-
364
- def _vf(self, auid_command: AuidCommand) -> None:
365
- self._validate_files(auid_command)
366
-
367
- def _version(self, string_command: StringCommand) -> None:
368
- self._do_string_command(string_command)
369
-
370
-
371
- def main() -> None:
372
- """
373
- Entry point for the debugpanel command line tool.
374
- """
375
- DebugPanelCli().run()
376
-
377
-
378
- if __name__ == '__main__':
379
- main()