meerschaum 2.2.6__py3-none-any.whl → 2.3.0__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.
- meerschaum/__init__.py +6 -1
- meerschaum/__main__.py +9 -9
- meerschaum/_internal/arguments/__init__.py +1 -1
- meerschaum/_internal/arguments/_parse_arguments.py +72 -6
- meerschaum/_internal/arguments/_parser.py +45 -15
- meerschaum/_internal/docs/index.py +265 -8
- meerschaum/_internal/entry.py +167 -37
- meerschaum/_internal/shell/Shell.py +290 -99
- meerschaum/_internal/shell/updates.py +175 -0
- meerschaum/actions/__init__.py +29 -17
- meerschaum/actions/api.py +12 -12
- meerschaum/actions/attach.py +113 -0
- meerschaum/actions/copy.py +68 -41
- meerschaum/actions/delete.py +112 -50
- meerschaum/actions/edit.py +3 -3
- meerschaum/actions/install.py +40 -32
- meerschaum/actions/pause.py +44 -27
- meerschaum/actions/register.py +19 -5
- meerschaum/actions/restart.py +107 -0
- meerschaum/actions/show.py +130 -159
- meerschaum/actions/start.py +161 -100
- meerschaum/actions/stop.py +78 -42
- meerschaum/actions/sync.py +3 -3
- meerschaum/actions/upgrade.py +28 -36
- meerschaum/api/_events.py +25 -1
- meerschaum/api/_oauth2.py +2 -0
- meerschaum/api/_websockets.py +2 -2
- meerschaum/api/dash/callbacks/jobs.py +36 -44
- meerschaum/api/dash/jobs.py +89 -78
- meerschaum/api/routes/__init__.py +1 -0
- meerschaum/api/routes/_actions.py +148 -17
- meerschaum/api/routes/_jobs.py +407 -0
- meerschaum/api/routes/_pipes.py +25 -25
- meerschaum/config/_default.py +1 -0
- meerschaum/config/_formatting.py +1 -0
- meerschaum/config/_jobs.py +1 -1
- meerschaum/config/_paths.py +11 -0
- meerschaum/config/_shell.py +84 -67
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +18 -0
- meerschaum/connectors/Connector.py +13 -7
- meerschaum/connectors/__init__.py +28 -15
- meerschaum/connectors/api/APIConnector.py +27 -1
- meerschaum/connectors/api/_actions.py +71 -6
- meerschaum/connectors/api/_jobs.py +368 -0
- meerschaum/connectors/api/_misc.py +1 -1
- meerschaum/connectors/api/_pipes.py +85 -84
- meerschaum/connectors/api/_request.py +13 -9
- meerschaum/connectors/parse.py +27 -15
- meerschaum/core/Pipe/_bootstrap.py +16 -8
- meerschaum/core/Pipe/_sync.py +3 -0
- meerschaum/jobs/_Executor.py +69 -0
- meerschaum/jobs/_Job.py +899 -0
- meerschaum/jobs/__init__.py +396 -0
- meerschaum/jobs/systemd.py +694 -0
- meerschaum/plugins/__init__.py +97 -12
- meerschaum/utils/daemon/Daemon.py +352 -147
- meerschaum/utils/daemon/FileDescriptorInterceptor.py +19 -10
- meerschaum/utils/daemon/RotatingFile.py +22 -8
- meerschaum/utils/daemon/StdinFile.py +121 -0
- meerschaum/utils/daemon/__init__.py +42 -27
- meerschaum/utils/daemon/_names.py +15 -13
- meerschaum/utils/formatting/__init__.py +83 -37
- meerschaum/utils/formatting/_jobs.py +146 -55
- meerschaum/utils/formatting/_shell.py +6 -0
- meerschaum/utils/misc.py +41 -22
- meerschaum/utils/packages/__init__.py +21 -15
- meerschaum/utils/packages/_packages.py +9 -6
- meerschaum/utils/process.py +9 -9
- meerschaum/utils/prompt.py +20 -7
- meerschaum/utils/schedule.py +21 -15
- meerschaum/utils/venv/__init__.py +2 -2
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/METADATA +22 -25
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/RECORD +80 -70
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/WHEEL +1 -1
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/LICENSE +0 -0
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/NOTICE +0 -0
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/top_level.txt +0 -0
- {meerschaum-2.2.6.dist-info → meerschaum-2.3.0.dist-info}/zip-safe +0 -0
@@ -0,0 +1,396 @@
|
|
1
|
+
#! /usr/bin/env python3
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
# vim:fenc=utf-8
|
4
|
+
|
5
|
+
"""
|
6
|
+
Higher-level utilities for managing `meerschaum.utils.daemon.Daemon`.
|
7
|
+
"""
|
8
|
+
|
9
|
+
import pathlib
|
10
|
+
|
11
|
+
import meerschaum as mrsm
|
12
|
+
from meerschaum.utils.typing import Dict, Optional, List, Callable, Any, SuccessTuple
|
13
|
+
|
14
|
+
from meerschaum.jobs._Job import Job, StopMonitoringLogs
|
15
|
+
from meerschaum.jobs._Executor import Executor
|
16
|
+
|
17
|
+
__all__ = (
|
18
|
+
'Job',
|
19
|
+
'systemd',
|
20
|
+
'get_jobs',
|
21
|
+
'get_filtered_jobs',
|
22
|
+
'get_restart_jobs',
|
23
|
+
'get_running_jobs',
|
24
|
+
'get_stopped_jobs',
|
25
|
+
'get_paused_jobs',
|
26
|
+
'get_restart_jobs',
|
27
|
+
'make_executor',
|
28
|
+
'Executor',
|
29
|
+
'check_restart_jobs',
|
30
|
+
'start_check_jobs_thread',
|
31
|
+
'stop_check_jobs_thread',
|
32
|
+
)
|
33
|
+
|
34
|
+
executor_types: List[str] = ['api', 'local']
|
35
|
+
|
36
|
+
|
37
|
+
def get_jobs(
|
38
|
+
executor_keys: Optional[str] = None,
|
39
|
+
include_hidden: bool = False,
|
40
|
+
combine_local_and_systemd: bool = True,
|
41
|
+
debug: bool = False,
|
42
|
+
) -> Dict[str, Job]:
|
43
|
+
"""
|
44
|
+
Return a dictionary of the existing jobs.
|
45
|
+
|
46
|
+
Parameters
|
47
|
+
----------
|
48
|
+
executor_keys: Optional[str], default None
|
49
|
+
If provided, return remote jobs on the given API instance.
|
50
|
+
Otherwise return local jobs.
|
51
|
+
|
52
|
+
include_hidden: bool, default False
|
53
|
+
If `True`, include jobs with the `hidden` attribute.
|
54
|
+
|
55
|
+
Returns
|
56
|
+
-------
|
57
|
+
A dictionary mapping job names to jobs.
|
58
|
+
"""
|
59
|
+
from meerschaum.connectors.parse import parse_executor_keys
|
60
|
+
include_local_and_system = (
|
61
|
+
combine_local_and_systemd
|
62
|
+
and str(executor_keys).split(':', maxsplit=1)[0] in ('None', 'local', 'systemd')
|
63
|
+
and get_executor_keys_from_context() == 'systemd'
|
64
|
+
)
|
65
|
+
|
66
|
+
def _get_local_jobs():
|
67
|
+
from meerschaum.utils.daemon import get_daemons
|
68
|
+
daemons = get_daemons()
|
69
|
+
jobs = {
|
70
|
+
daemon.daemon_id: Job(name=daemon.daemon_id, executor_keys='local')
|
71
|
+
for daemon in daemons
|
72
|
+
}
|
73
|
+
return {
|
74
|
+
name: job
|
75
|
+
for name, job in jobs.items()
|
76
|
+
if (include_hidden or not job.hidden) and not job._is_externally_managed
|
77
|
+
|
78
|
+
}
|
79
|
+
|
80
|
+
def _get_systemd_jobs():
|
81
|
+
conn = mrsm.get_connector('systemd')
|
82
|
+
jobs = conn.get_jobs(debug=debug)
|
83
|
+
return {
|
84
|
+
name: job
|
85
|
+
for name, job in jobs.items()
|
86
|
+
if include_hidden or not job.hidden
|
87
|
+
}
|
88
|
+
|
89
|
+
if include_local_and_system:
|
90
|
+
local_jobs = _get_local_jobs()
|
91
|
+
systemd_jobs = _get_systemd_jobs()
|
92
|
+
shared_jobs = set(local_jobs) & set(systemd_jobs)
|
93
|
+
if shared_jobs:
|
94
|
+
from meerschaum.utils.misc import items_str
|
95
|
+
from meerschaum.utils.warnings import warn
|
96
|
+
warn(
|
97
|
+
"Job"
|
98
|
+
+ ('s' if len(shared_jobs) != 1 else '')
|
99
|
+
+ f" {items_str(list(shared_jobs))} "
|
100
|
+
+ "exist"
|
101
|
+
+ ('s' if len(shared_jobs) == 1 else '')
|
102
|
+
+ " in both `local` and `systemd`.",
|
103
|
+
stack=False,
|
104
|
+
)
|
105
|
+
return {**local_jobs, **systemd_jobs}
|
106
|
+
|
107
|
+
if executor_keys == 'local':
|
108
|
+
return _get_local_jobs()
|
109
|
+
|
110
|
+
if executor_keys == 'systemd':
|
111
|
+
return _get_systemd_jobs()
|
112
|
+
|
113
|
+
try:
|
114
|
+
_ = parse_executor_keys(executor_keys, construct=False)
|
115
|
+
conn = parse_executor_keys(executor_keys)
|
116
|
+
jobs = conn.get_jobs(debug=debug)
|
117
|
+
return {
|
118
|
+
name: job
|
119
|
+
for name, job in jobs.items()
|
120
|
+
if include_hidden or not job.hidden
|
121
|
+
}
|
122
|
+
except Exception:
|
123
|
+
return {}
|
124
|
+
|
125
|
+
|
126
|
+
def get_filtered_jobs(
|
127
|
+
executor_keys: Optional[str] = None,
|
128
|
+
filter_list: Optional[List[str]] = None,
|
129
|
+
include_hidden: bool = False,
|
130
|
+
warn: bool = False,
|
131
|
+
debug: bool = False,
|
132
|
+
) -> Dict[str, Job]:
|
133
|
+
"""
|
134
|
+
Return a list of jobs filtered by the user.
|
135
|
+
"""
|
136
|
+
from meerschaum.utils.warnings import warn as _warn
|
137
|
+
jobs = get_jobs(executor_keys, include_hidden=True, debug=debug)
|
138
|
+
if not filter_list:
|
139
|
+
return {
|
140
|
+
name: job
|
141
|
+
for name, job in jobs.items()
|
142
|
+
if include_hidden or not job.hidden
|
143
|
+
}
|
144
|
+
|
145
|
+
jobs_to_return = {}
|
146
|
+
for name in filter_list:
|
147
|
+
job = jobs.get(name, None)
|
148
|
+
if job is None:
|
149
|
+
if warn:
|
150
|
+
_warn(
|
151
|
+
f"Job '{name}' does not exist.",
|
152
|
+
stack=False,
|
153
|
+
)
|
154
|
+
continue
|
155
|
+
jobs_to_return[name] = job
|
156
|
+
|
157
|
+
return jobs_to_return
|
158
|
+
|
159
|
+
|
160
|
+
def get_restart_jobs(
|
161
|
+
executor_keys: Optional[str] = None,
|
162
|
+
jobs: Optional[Dict[str, Job]] = None,
|
163
|
+
include_hidden: bool = False,
|
164
|
+
debug: bool = False,
|
165
|
+
) -> Dict[str, Job]:
|
166
|
+
"""
|
167
|
+
Return jobs which were created with `--restart` or `--loop`.
|
168
|
+
"""
|
169
|
+
if jobs is None:
|
170
|
+
jobs = get_jobs(executor_keys, include_hidden=include_hidden, debug=debug)
|
171
|
+
|
172
|
+
return {
|
173
|
+
name: job
|
174
|
+
for name, job in jobs.items()
|
175
|
+
if job.restart
|
176
|
+
}
|
177
|
+
|
178
|
+
|
179
|
+
def get_running_jobs(
|
180
|
+
executor_keys: Optional[str] = None,
|
181
|
+
jobs: Optional[Dict[str, Job]] = None,
|
182
|
+
include_hidden: bool = False,
|
183
|
+
debug: bool = False,
|
184
|
+
) -> Dict[str, Job]:
|
185
|
+
"""
|
186
|
+
Return a dictionary of running jobs.
|
187
|
+
"""
|
188
|
+
if jobs is None:
|
189
|
+
jobs = get_jobs(executor_keys, include_hidden=include_hidden, debug=debug)
|
190
|
+
|
191
|
+
return {
|
192
|
+
name: job
|
193
|
+
for name, job in jobs.items()
|
194
|
+
if job.status == 'running'
|
195
|
+
}
|
196
|
+
|
197
|
+
|
198
|
+
def get_paused_jobs(
|
199
|
+
executor_keys: Optional[str] = None,
|
200
|
+
jobs: Optional[Dict[str, Job]] = None,
|
201
|
+
include_hidden: bool = False,
|
202
|
+
debug: bool = False,
|
203
|
+
) -> Dict[str, Job]:
|
204
|
+
"""
|
205
|
+
Return a dictionary of paused jobs.
|
206
|
+
"""
|
207
|
+
if jobs is None:
|
208
|
+
jobs = get_jobs(executor_keys, include_hidden=include_hidden, debug=debug)
|
209
|
+
|
210
|
+
return {
|
211
|
+
name: job
|
212
|
+
for name, job in jobs.items()
|
213
|
+
if job.status == 'paused'
|
214
|
+
}
|
215
|
+
|
216
|
+
|
217
|
+
def get_stopped_jobs(
|
218
|
+
executor_keys: Optional[str] = None,
|
219
|
+
jobs: Optional[Dict[str, Job]] = None,
|
220
|
+
include_hidden: bool = False,
|
221
|
+
debug: bool = False,
|
222
|
+
) -> Dict[str, Job]:
|
223
|
+
"""
|
224
|
+
Return a dictionary of stopped jobs.
|
225
|
+
"""
|
226
|
+
if jobs is None:
|
227
|
+
jobs = get_jobs(executor_keys, include_hidden=include_hidden, debug=debug)
|
228
|
+
|
229
|
+
return {
|
230
|
+
name: job
|
231
|
+
for name, job in jobs.items()
|
232
|
+
if job.status == 'stopped'
|
233
|
+
}
|
234
|
+
|
235
|
+
|
236
|
+
def make_executor(cls):
|
237
|
+
"""
|
238
|
+
Register a class as an `Executor`.
|
239
|
+
"""
|
240
|
+
import re
|
241
|
+
from meerschaum.connectors import make_connector
|
242
|
+
suffix_regex = r'executor$'
|
243
|
+
typ = re.sub(suffix_regex, '', cls.__name__.lower())
|
244
|
+
if typ not in executor_types:
|
245
|
+
executor_types.append(typ)
|
246
|
+
return make_connector(cls, _is_executor=True)
|
247
|
+
|
248
|
+
|
249
|
+
def check_restart_jobs(
|
250
|
+
executor_keys: Optional[str] = 'local',
|
251
|
+
jobs: Optional[Dict[str, Job]] = None,
|
252
|
+
include_hidden: bool = True,
|
253
|
+
silent: bool = False,
|
254
|
+
debug: bool = False,
|
255
|
+
) -> SuccessTuple:
|
256
|
+
"""
|
257
|
+
Restart any stopped jobs which were created with `--restart`.
|
258
|
+
|
259
|
+
Parameters
|
260
|
+
----------
|
261
|
+
executor_keys: Optional[str], default None
|
262
|
+
If provided, check jobs on the given remote API instance.
|
263
|
+
Otherwise check local jobs.
|
264
|
+
|
265
|
+
include_hidden: bool, default True
|
266
|
+
If `True`, include hidden jobs in the check.
|
267
|
+
|
268
|
+
silent: bool, default False
|
269
|
+
If `True`, do not print the restart success message.
|
270
|
+
"""
|
271
|
+
from meerschaum.utils.misc import items_str
|
272
|
+
|
273
|
+
if jobs is None:
|
274
|
+
jobs = get_jobs(
|
275
|
+
executor_keys,
|
276
|
+
include_hidden=include_hidden,
|
277
|
+
debug=debug,
|
278
|
+
)
|
279
|
+
|
280
|
+
if not jobs:
|
281
|
+
return True, "No jobs to restart."
|
282
|
+
|
283
|
+
results = {}
|
284
|
+
for name, job in jobs.items():
|
285
|
+
check_success, check_msg = job.check_restart()
|
286
|
+
results[job.name] = (check_success, check_msg)
|
287
|
+
if not silent:
|
288
|
+
mrsm.pprint((check_success, check_msg))
|
289
|
+
|
290
|
+
success_names = [name for name, (check_success, check_msg) in results.items() if check_success]
|
291
|
+
fail_names = [name for name, (check_success, check_msg) in results.items() if not check_success]
|
292
|
+
success = len(success_names) == len(jobs)
|
293
|
+
msg = (
|
294
|
+
(
|
295
|
+
"Successfully restarted job"
|
296
|
+
+ ('s' if len(success_names) != 1 else '')
|
297
|
+
+ ' ' + items_str(success_names) + '.'
|
298
|
+
)
|
299
|
+
if success
|
300
|
+
else (
|
301
|
+
"Failed to restart job"
|
302
|
+
+ ('s' if len(success_names) != 1 else '')
|
303
|
+
+ ' ' + items_str(fail_names) + '.'
|
304
|
+
)
|
305
|
+
)
|
306
|
+
return success, msg
|
307
|
+
|
308
|
+
|
309
|
+
def _check_restart_jobs_against_lock(*args, **kwargs):
|
310
|
+
from meerschaum.config.paths import CHECK_JOBS_LOCK_PATH
|
311
|
+
fasteners = mrsm.attempt_import('fasteners')
|
312
|
+
lock = fasteners.InterProcessLock(CHECK_JOBS_LOCK_PATH)
|
313
|
+
with lock:
|
314
|
+
check_restart_jobs(*args, **kwargs)
|
315
|
+
|
316
|
+
|
317
|
+
_check_loop_stop_thread = None
|
318
|
+
def start_check_jobs_thread():
|
319
|
+
"""
|
320
|
+
Start a thread to regularly monitor jobs.
|
321
|
+
"""
|
322
|
+
import atexit
|
323
|
+
from functools import partial
|
324
|
+
from meerschaum.utils.threading import RepeatTimer
|
325
|
+
from meerschaum.config.static import STATIC_CONFIG
|
326
|
+
|
327
|
+
global _check_loop_stop_thread
|
328
|
+
sleep_seconds = STATIC_CONFIG['jobs']['check_restart_seconds']
|
329
|
+
|
330
|
+
_check_loop_stop_thread = RepeatTimer(
|
331
|
+
sleep_seconds,
|
332
|
+
partial(
|
333
|
+
_check_restart_jobs_against_lock,
|
334
|
+
silent=True,
|
335
|
+
)
|
336
|
+
)
|
337
|
+
_check_loop_stop_thread.daemon = True
|
338
|
+
atexit.register(stop_check_jobs_thread)
|
339
|
+
|
340
|
+
_check_loop_stop_thread.start()
|
341
|
+
return _check_loop_stop_thread
|
342
|
+
|
343
|
+
|
344
|
+
def stop_check_jobs_thread():
|
345
|
+
"""
|
346
|
+
Stop the job monitoring thread.
|
347
|
+
"""
|
348
|
+
from meerschaum.config.paths import CHECK_JOBS_LOCK_PATH
|
349
|
+
from meerschaum.utils.warnings import warn
|
350
|
+
if _check_loop_stop_thread is None:
|
351
|
+
return
|
352
|
+
|
353
|
+
_check_loop_stop_thread.cancel()
|
354
|
+
|
355
|
+
try:
|
356
|
+
if CHECK_JOBS_LOCK_PATH.exists():
|
357
|
+
CHECK_JOBS_LOCK_PATH.unlink()
|
358
|
+
except Exception as e:
|
359
|
+
warn(f"Failed to remove check jobs lock file:\n{e}")
|
360
|
+
|
361
|
+
|
362
|
+
_context_keys = None
|
363
|
+
def get_executor_keys_from_context() -> str:
|
364
|
+
"""
|
365
|
+
If we are running on the host with the default root, default to `'systemd'`.
|
366
|
+
Otherwise return `'local'`.
|
367
|
+
"""
|
368
|
+
global _context_keys
|
369
|
+
|
370
|
+
if _context_keys is not None:
|
371
|
+
return _context_keys
|
372
|
+
|
373
|
+
from meerschaum.config.paths import ROOT_DIR_PATH, DEFAULT_ROOT_DIR_PATH
|
374
|
+
from meerschaum.utils.misc import is_systemd_available
|
375
|
+
|
376
|
+
_context_keys = (
|
377
|
+
'systemd'
|
378
|
+
if is_systemd_available() and ROOT_DIR_PATH == DEFAULT_ROOT_DIR_PATH
|
379
|
+
else 'local'
|
380
|
+
)
|
381
|
+
return _context_keys
|
382
|
+
|
383
|
+
|
384
|
+
def _install_healthcheck_job() -> SuccessTuple:
|
385
|
+
"""
|
386
|
+
Install the systemd job which checks local jobs.
|
387
|
+
"""
|
388
|
+
if get_executor_keys_from_context() != 'systemd':
|
389
|
+
return False, "Not running systemd."
|
390
|
+
|
391
|
+
job = Job(
|
392
|
+
'.local-healthcheck',
|
393
|
+
['restart', 'jobs', '-e', 'local', '--loop'],
|
394
|
+
executor_keys='systemd',
|
395
|
+
)
|
396
|
+
return job.start()
|