meerschaum 2.3.0.dev3__py3-none-any.whl → 2.3.0rc2__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 +3 -2
- meerschaum/__main__.py +0 -5
- meerschaum/_internal/arguments/__init__.py +1 -1
- meerschaum/_internal/arguments/_parse_arguments.py +56 -2
- meerschaum/_internal/arguments/_parser.py +6 -2
- meerschaum/_internal/entry.py +94 -23
- meerschaum/_internal/shell/Shell.py +112 -35
- meerschaum/actions/attach.py +12 -7
- meerschaum/actions/copy.py +68 -41
- meerschaum/actions/delete.py +75 -26
- meerschaum/actions/edit.py +3 -3
- meerschaum/actions/install.py +40 -32
- meerschaum/actions/pause.py +44 -27
- meerschaum/actions/restart.py +107 -0
- meerschaum/actions/show.py +8 -8
- meerschaum/actions/start.py +27 -46
- meerschaum/actions/stop.py +11 -4
- meerschaum/api/_events.py +10 -3
- meerschaum/api/dash/jobs.py +69 -70
- meerschaum/api/routes/_actions.py +8 -3
- meerschaum/api/routes/_jobs.py +37 -19
- meerschaum/config/_default.py +1 -1
- meerschaum/config/_paths.py +5 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +5 -0
- meerschaum/connectors/Connector.py +13 -7
- meerschaum/connectors/__init__.py +21 -5
- meerschaum/connectors/api/APIConnector.py +3 -0
- meerschaum/connectors/api/_jobs.py +30 -3
- meerschaum/connectors/parse.py +10 -14
- meerschaum/core/Pipe/_bootstrap.py +16 -8
- meerschaum/jobs/_Executor.py +69 -0
- meerschaum/{utils/jobs → jobs}/_Job.py +169 -21
- meerschaum/jobs/_LocalExecutor.py +88 -0
- meerschaum/jobs/_SystemdExecutor.py +613 -0
- meerschaum/jobs/__init__.py +388 -0
- meerschaum/plugins/__init__.py +6 -6
- meerschaum/utils/daemon/Daemon.py +7 -0
- meerschaum/utils/daemon/RotatingFile.py +5 -2
- meerschaum/utils/daemon/StdinFile.py +12 -2
- meerschaum/utils/daemon/__init__.py +2 -0
- meerschaum/utils/formatting/_jobs.py +49 -25
- meerschaum/utils/misc.py +23 -5
- meerschaum/utils/packages/_packages.py +7 -4
- meerschaum/utils/process.py +9 -9
- meerschaum/utils/venv/__init__.py +2 -2
- {meerschaum-2.3.0.dev3.dist-info → meerschaum-2.3.0rc2.dist-info}/METADATA +14 -17
- {meerschaum-2.3.0.dev3.dist-info → meerschaum-2.3.0rc2.dist-info}/RECORD +54 -50
- meerschaum/utils/jobs/__init__.py +0 -245
- {meerschaum-2.3.0.dev3.dist-info → meerschaum-2.3.0rc2.dist-info}/LICENSE +0 -0
- {meerschaum-2.3.0.dev3.dist-info → meerschaum-2.3.0rc2.dist-info}/NOTICE +0 -0
- {meerschaum-2.3.0.dev3.dist-info → meerschaum-2.3.0rc2.dist-info}/WHEEL +0 -0
- {meerschaum-2.3.0.dev3.dist-info → meerschaum-2.3.0rc2.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.3.0.dev3.dist-info → meerschaum-2.3.0rc2.dist-info}/top_level.txt +0 -0
- {meerschaum-2.3.0.dev3.dist-info → meerschaum-2.3.0rc2.dist-info}/zip-safe +0 -0
@@ -0,0 +1,388 @@
|
|
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
|
+
'get_jobs',
|
20
|
+
'get_filtered_jobs',
|
21
|
+
'get_restart_jobs',
|
22
|
+
'get_running_jobs',
|
23
|
+
'get_stopped_jobs',
|
24
|
+
'get_paused_jobs',
|
25
|
+
'get_restart_jobs',
|
26
|
+
'Executor',
|
27
|
+
'make_executor',
|
28
|
+
'check_restart_jobs',
|
29
|
+
'start_check_jobs_thread',
|
30
|
+
'stop_check_jobs_thread',
|
31
|
+
)
|
32
|
+
|
33
|
+
executor_types: List[str] = ['api', 'local']
|
34
|
+
|
35
|
+
|
36
|
+
def get_jobs(
|
37
|
+
executor_keys: Optional[str] = None,
|
38
|
+
include_hidden: bool = False,
|
39
|
+
combine_local_and_systemd: bool = True,
|
40
|
+
debug: bool = False,
|
41
|
+
) -> Dict[str, Job]:
|
42
|
+
"""
|
43
|
+
Return a dictionary of the existing jobs.
|
44
|
+
|
45
|
+
Parameters
|
46
|
+
----------
|
47
|
+
executor_keys: Optional[str], default None
|
48
|
+
If provided, return remote jobs on the given API instance.
|
49
|
+
Otherwise return local jobs.
|
50
|
+
|
51
|
+
include_hidden: bool, default False
|
52
|
+
If `True`, include jobs with the `hidden` attribute.
|
53
|
+
|
54
|
+
Returns
|
55
|
+
-------
|
56
|
+
A dictionary mapping job names to jobs.
|
57
|
+
"""
|
58
|
+
from meerschaum.connectors.parse import parse_executor_keys
|
59
|
+
include_local_and_system = (
|
60
|
+
combine_local_and_systemd
|
61
|
+
and str(executor_keys).split(':', maxsplit=1)[0] in ('None', 'local', 'systemd')
|
62
|
+
and get_executor_keys_from_context() == 'systemd'
|
63
|
+
)
|
64
|
+
|
65
|
+
def _get_local_jobs():
|
66
|
+
from meerschaum.utils.daemon import get_daemons
|
67
|
+
daemons = get_daemons()
|
68
|
+
jobs = {
|
69
|
+
daemon.daemon_id: Job(name=daemon.daemon_id, executor_keys='local')
|
70
|
+
for daemon in daemons
|
71
|
+
}
|
72
|
+
return {
|
73
|
+
name: job
|
74
|
+
for name, job in jobs.items()
|
75
|
+
if include_hidden or not job.hidden
|
76
|
+
}
|
77
|
+
|
78
|
+
def _get_systemd_jobs():
|
79
|
+
conn = mrsm.get_connector('systemd')
|
80
|
+
jobs = conn.get_jobs(debug=debug)
|
81
|
+
return {
|
82
|
+
name: job
|
83
|
+
for name, job in jobs.items()
|
84
|
+
if include_hidden or not job.hidden
|
85
|
+
}
|
86
|
+
|
87
|
+
if include_local_and_system:
|
88
|
+
local_jobs = _get_local_jobs()
|
89
|
+
systemd_jobs = _get_systemd_jobs()
|
90
|
+
shared_jobs = set(local_jobs) & set(systemd_jobs)
|
91
|
+
if shared_jobs:
|
92
|
+
from meerschaum.utils.misc import items_str
|
93
|
+
from meerschaum.utils.warnings import warn
|
94
|
+
warn(
|
95
|
+
"Job"
|
96
|
+
+ ('s' if len(shared_jobs) != 1 else '')
|
97
|
+
+ f" {items_str(list(shared_jobs))} "
|
98
|
+
+ "exist"
|
99
|
+
+ ('s' if len(shared_jobs) == 1 else '')
|
100
|
+
+ " in both `local` and `systemd`.",
|
101
|
+
stack=False,
|
102
|
+
)
|
103
|
+
return {**local_jobs, **systemd_jobs}
|
104
|
+
|
105
|
+
try:
|
106
|
+
_ = parse_executor_keys(executor_keys, construct=False)
|
107
|
+
conn = parse_executor_keys(executor_keys)
|
108
|
+
jobs = conn.get_jobs(debug=debug)
|
109
|
+
return {
|
110
|
+
name: job
|
111
|
+
for name, job in jobs.items()
|
112
|
+
if include_hidden or not job.hidden
|
113
|
+
}
|
114
|
+
except Exception:
|
115
|
+
return {}
|
116
|
+
|
117
|
+
|
118
|
+
def get_filtered_jobs(
|
119
|
+
executor_keys: Optional[str] = None,
|
120
|
+
filter_list: Optional[List[str]] = None,
|
121
|
+
include_hidden: bool = False,
|
122
|
+
warn: bool = False,
|
123
|
+
debug: bool = False,
|
124
|
+
) -> Dict[str, Job]:
|
125
|
+
"""
|
126
|
+
Return a list of jobs filtered by the user.
|
127
|
+
"""
|
128
|
+
from meerschaum.utils.warnings import warn as _warn
|
129
|
+
jobs = get_jobs(executor_keys, include_hidden=True, debug=debug)
|
130
|
+
if not filter_list:
|
131
|
+
return {
|
132
|
+
name: job
|
133
|
+
for name, job in jobs.items()
|
134
|
+
if include_hidden or not job.hidden
|
135
|
+
}
|
136
|
+
|
137
|
+
jobs_to_return = {}
|
138
|
+
for name in filter_list:
|
139
|
+
job = jobs.get(name, None)
|
140
|
+
if job is None:
|
141
|
+
if warn:
|
142
|
+
_warn(
|
143
|
+
f"Job '{name}' does not exist.",
|
144
|
+
stack=False,
|
145
|
+
)
|
146
|
+
continue
|
147
|
+
jobs_to_return[name] = job
|
148
|
+
|
149
|
+
return jobs_to_return
|
150
|
+
|
151
|
+
|
152
|
+
def get_restart_jobs(
|
153
|
+
executor_keys: Optional[str] = None,
|
154
|
+
jobs: Optional[Dict[str, Job]] = None,
|
155
|
+
include_hidden: bool = False,
|
156
|
+
debug: bool = False,
|
157
|
+
) -> Dict[str, Job]:
|
158
|
+
"""
|
159
|
+
Return jobs which were created with `--restart` or `--loop`.
|
160
|
+
"""
|
161
|
+
if jobs is None:
|
162
|
+
jobs = get_jobs(executor_keys, include_hidden=include_hidden, debug=debug)
|
163
|
+
|
164
|
+
return {
|
165
|
+
name: job
|
166
|
+
for name, job in jobs.items()
|
167
|
+
if job.restart
|
168
|
+
}
|
169
|
+
|
170
|
+
|
171
|
+
def get_running_jobs(
|
172
|
+
executor_keys: Optional[str] = None,
|
173
|
+
jobs: Optional[Dict[str, Job]] = None,
|
174
|
+
include_hidden: bool = False,
|
175
|
+
debug: bool = False,
|
176
|
+
) -> Dict[str, Job]:
|
177
|
+
"""
|
178
|
+
Return a dictionary of running jobs.
|
179
|
+
"""
|
180
|
+
if jobs is None:
|
181
|
+
jobs = get_jobs(executor_keys, include_hidden=include_hidden, debug=debug)
|
182
|
+
|
183
|
+
return {
|
184
|
+
name: job
|
185
|
+
for name, job in jobs.items()
|
186
|
+
if job.status == 'running'
|
187
|
+
}
|
188
|
+
|
189
|
+
|
190
|
+
def get_paused_jobs(
|
191
|
+
executor_keys: Optional[str] = None,
|
192
|
+
jobs: Optional[Dict[str, Job]] = None,
|
193
|
+
include_hidden: bool = False,
|
194
|
+
debug: bool = False,
|
195
|
+
) -> Dict[str, Job]:
|
196
|
+
"""
|
197
|
+
Return a dictionary of paused jobs.
|
198
|
+
"""
|
199
|
+
if jobs is None:
|
200
|
+
jobs = get_jobs(executor_keys, include_hidden=include_hidden, debug=debug)
|
201
|
+
|
202
|
+
return {
|
203
|
+
name: job
|
204
|
+
for name, job in jobs.items()
|
205
|
+
if job.status == 'paused'
|
206
|
+
}
|
207
|
+
|
208
|
+
|
209
|
+
def get_stopped_jobs(
|
210
|
+
executor_keys: Optional[str] = None,
|
211
|
+
jobs: Optional[Dict[str, Job]] = None,
|
212
|
+
include_hidden: bool = False,
|
213
|
+
debug: bool = False,
|
214
|
+
) -> Dict[str, Job]:
|
215
|
+
"""
|
216
|
+
Return a dictionary of stopped jobs.
|
217
|
+
"""
|
218
|
+
if jobs is None:
|
219
|
+
jobs = get_jobs(executor_keys, include_hidden=include_hidden, debug=debug)
|
220
|
+
|
221
|
+
return {
|
222
|
+
name: job
|
223
|
+
for name, job in jobs.items()
|
224
|
+
if job.status == 'stopped'
|
225
|
+
}
|
226
|
+
|
227
|
+
|
228
|
+
def make_executor(cls):
|
229
|
+
"""
|
230
|
+
Register a class as an `Executor`.
|
231
|
+
"""
|
232
|
+
import re
|
233
|
+
from meerschaum.connectors import make_connector
|
234
|
+
suffix_regex = r'executor$'
|
235
|
+
typ = re.sub(suffix_regex, '', cls.__name__.lower())
|
236
|
+
if typ not in executor_types:
|
237
|
+
executor_types.append(typ)
|
238
|
+
return make_connector(cls, _is_executor=True)
|
239
|
+
|
240
|
+
|
241
|
+
def check_restart_jobs(
|
242
|
+
executor_keys: Optional[str] = 'local',
|
243
|
+
jobs: Optional[Dict[str, Job]] = None,
|
244
|
+
include_hidden: bool = True,
|
245
|
+
silent: bool = False,
|
246
|
+
debug: bool = False,
|
247
|
+
) -> SuccessTuple:
|
248
|
+
"""
|
249
|
+
Restart any stopped jobs which were created with `--restart`.
|
250
|
+
|
251
|
+
Parameters
|
252
|
+
----------
|
253
|
+
executor_keys: Optional[str], default None
|
254
|
+
If provided, check jobs on the given remote API instance.
|
255
|
+
Otherwise check local jobs.
|
256
|
+
|
257
|
+
include_hidden: bool, default True
|
258
|
+
If `True`, include hidden jobs in the check.
|
259
|
+
|
260
|
+
silent: bool, default False
|
261
|
+
If `True`, do not print the restart success message.
|
262
|
+
"""
|
263
|
+
from meerschaum.utils.misc import items_str
|
264
|
+
|
265
|
+
if jobs is None:
|
266
|
+
jobs = get_jobs(
|
267
|
+
executor_keys,
|
268
|
+
include_hidden=include_hidden,
|
269
|
+
debug=debug,
|
270
|
+
)
|
271
|
+
|
272
|
+
if not jobs:
|
273
|
+
return True, "No jobs to restart."
|
274
|
+
|
275
|
+
results = {}
|
276
|
+
for name, job in jobs.items():
|
277
|
+
check_success, check_msg = job.check_restart()
|
278
|
+
results[job.name] = (check_success, check_msg)
|
279
|
+
if not silent:
|
280
|
+
mrsm.pprint((check_success, check_msg))
|
281
|
+
|
282
|
+
success_names = [name for name, (check_success, check_msg) in results.items() if check_success]
|
283
|
+
fail_names = [name for name, (check_success, check_msg) in results.items() if not check_success]
|
284
|
+
success = len(success_names) == len(jobs)
|
285
|
+
msg = (
|
286
|
+
(
|
287
|
+
"Successfully restarted job"
|
288
|
+
+ ('s' if len(success_names) != 1 else '')
|
289
|
+
+ ' ' + items_str(success_names) + '.'
|
290
|
+
)
|
291
|
+
if success
|
292
|
+
else (
|
293
|
+
"Failed to restart job"
|
294
|
+
+ ('s' if len(success_names) != 1 else '')
|
295
|
+
+ ' ' + items_str(fail_names) + '.'
|
296
|
+
)
|
297
|
+
)
|
298
|
+
return success, msg
|
299
|
+
|
300
|
+
|
301
|
+
def _check_restart_jobs_against_lock(*args, **kwargs):
|
302
|
+
from meerschaum.config.paths import CHECK_JOBS_LOCK_PATH
|
303
|
+
fasteners = mrsm.attempt_import('fasteners')
|
304
|
+
lock = fasteners.InterProcessLock(CHECK_JOBS_LOCK_PATH)
|
305
|
+
with lock:
|
306
|
+
check_restart_jobs(*args, **kwargs)
|
307
|
+
|
308
|
+
|
309
|
+
_check_loop_stop_thread = None
|
310
|
+
def start_check_jobs_thread():
|
311
|
+
"""
|
312
|
+
Start a thread to regularly monitor jobs.
|
313
|
+
"""
|
314
|
+
import atexit
|
315
|
+
from functools import partial
|
316
|
+
from meerschaum.utils.threading import RepeatTimer
|
317
|
+
from meerschaum.config.static import STATIC_CONFIG
|
318
|
+
|
319
|
+
global _check_loop_stop_thread
|
320
|
+
sleep_seconds = STATIC_CONFIG['jobs']['check_restart_seconds']
|
321
|
+
|
322
|
+
_check_loop_stop_thread = RepeatTimer(
|
323
|
+
sleep_seconds,
|
324
|
+
partial(
|
325
|
+
_check_restart_jobs_against_lock,
|
326
|
+
silent=True,
|
327
|
+
)
|
328
|
+
)
|
329
|
+
_check_loop_stop_thread.daemon = True
|
330
|
+
atexit.register(stop_check_jobs_thread)
|
331
|
+
|
332
|
+
_check_loop_stop_thread.start()
|
333
|
+
return _check_loop_stop_thread
|
334
|
+
|
335
|
+
|
336
|
+
def stop_check_jobs_thread():
|
337
|
+
"""
|
338
|
+
Stop the job monitoring thread.
|
339
|
+
"""
|
340
|
+
from meerschaum.config.paths import CHECK_JOBS_LOCK_PATH
|
341
|
+
from meerschaum.utils.warnings import warn
|
342
|
+
if _check_loop_stop_thread is None:
|
343
|
+
return
|
344
|
+
|
345
|
+
_check_loop_stop_thread.cancel()
|
346
|
+
|
347
|
+
try:
|
348
|
+
if CHECK_JOBS_LOCK_PATH.exists():
|
349
|
+
CHECK_JOBS_LOCK_PATH.unlink()
|
350
|
+
except Exception as e:
|
351
|
+
warn(f"Failed to remove check jobs lock file:\n{e}")
|
352
|
+
|
353
|
+
|
354
|
+
_context_keys = None
|
355
|
+
def get_executor_keys_from_context() -> str:
|
356
|
+
"""
|
357
|
+
If we are running on the host with the default root, default to `'systemd'`.
|
358
|
+
Otherwise return `'local'`.
|
359
|
+
"""
|
360
|
+
global _context_keys
|
361
|
+
|
362
|
+
if _context_keys is not None:
|
363
|
+
return _context_keys
|
364
|
+
|
365
|
+
from meerschaum.config.paths import ROOT_DIR_PATH, DEFAULT_ROOT_DIR_PATH
|
366
|
+
from meerschaum.utils.misc import is_systemd_available
|
367
|
+
|
368
|
+
_context_keys = (
|
369
|
+
'systemd'
|
370
|
+
if is_systemd_available() and ROOT_DIR_PATH == DEFAULT_ROOT_DIR_PATH
|
371
|
+
else 'local'
|
372
|
+
)
|
373
|
+
return _context_keys
|
374
|
+
|
375
|
+
|
376
|
+
def _install_healthcheck_job() -> SuccessTuple:
|
377
|
+
"""
|
378
|
+
Install the systemd job which checks local jobs.
|
379
|
+
"""
|
380
|
+
if get_executor_keys_from_context() != 'systemd':
|
381
|
+
return False, "Not running systemd."
|
382
|
+
|
383
|
+
job = Job(
|
384
|
+
'.local_healthcheck',
|
385
|
+
['restart', 'jobs', '-e', 'local', '--loop'],
|
386
|
+
executor_keys='systemd',
|
387
|
+
)
|
388
|
+
return job.start()
|
meerschaum/plugins/__init__.py
CHANGED
@@ -37,12 +37,12 @@ __pdoc__ = {
|
|
37
37
|
}
|
38
38
|
|
39
39
|
def make_action(
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
40
|
+
function: Callable[[Any], Any],
|
41
|
+
shell: bool = False,
|
42
|
+
activate: bool = True,
|
43
|
+
deactivate: bool = True,
|
44
|
+
debug: bool = False
|
45
|
+
) -> Callable[[Any], Any]:
|
46
46
|
"""
|
47
47
|
Make a function a Meerschaum action. Useful for plugins that are adding multiple actions.
|
48
48
|
|
@@ -294,6 +294,7 @@ class Daemon:
|
|
294
294
|
with self._daemon_context:
|
295
295
|
sys.stdin = self.stdin_file
|
296
296
|
os.environ[STATIC_CONFIG['environment']['daemon_id']] = self.daemon_id
|
297
|
+
os.environ['PYTHONUNBUFFERED'] = '1'
|
297
298
|
self.rotating_log.refresh_files(start_interception=True)
|
298
299
|
result = None
|
299
300
|
try:
|
@@ -596,6 +597,9 @@ class Daemon:
|
|
596
597
|
if action not in ('quit', 'kill', 'pause'):
|
597
598
|
return False, f"Unsupported action '{action}'."
|
598
599
|
|
600
|
+
if not self.stop_path.parent.exists():
|
601
|
+
self.stop_path.parent.mkdir(parents=True, exist_ok=True)
|
602
|
+
|
599
603
|
with open(self.stop_path, 'w+', encoding='utf-8') as f:
|
600
604
|
json.dump(
|
601
605
|
{
|
@@ -810,6 +814,9 @@ class Daemon:
|
|
810
814
|
"""
|
811
815
|
Return the stdin file path.
|
812
816
|
"""
|
817
|
+
if '_blocking_stdin_file_path' in self.__dict__:
|
818
|
+
return self._blocking_stdin_file_path
|
819
|
+
|
813
820
|
return self.path / 'input.stdin.block'
|
814
821
|
|
815
822
|
@property
|
@@ -38,7 +38,7 @@ class RotatingFile(io.IOBase):
|
|
38
38
|
max_file_size: Optional[int] = None,
|
39
39
|
redirect_streams: bool = False,
|
40
40
|
write_timestamps: bool = False,
|
41
|
-
timestamp_format: str =
|
41
|
+
timestamp_format: Optional[str] = None,
|
42
42
|
):
|
43
43
|
"""
|
44
44
|
Create a file-like object which manages other files.
|
@@ -64,14 +64,17 @@ class RotatingFile(io.IOBase):
|
|
64
64
|
write_timestamps: bool, default False
|
65
65
|
If `True`, prepend the current UTC timestamp to each line of the file.
|
66
66
|
|
67
|
-
timestamp_format: str, default
|
67
|
+
timestamp_format: str, default None
|
68
68
|
If `write_timestamps` is `True`, use this format for the timestamps.
|
69
|
+
Defaults to `'%Y-%m-%d %H:%M'`.
|
69
70
|
"""
|
70
71
|
self.file_path = pathlib.Path(file_path)
|
71
72
|
if num_files_to_keep is None:
|
72
73
|
num_files_to_keep = get_config('jobs', 'logs', 'num_files_to_keep')
|
73
74
|
if max_file_size is None:
|
74
75
|
max_file_size = get_config('jobs', 'logs', 'max_file_size')
|
76
|
+
if timestamp_format is None:
|
77
|
+
timestamp_format = get_config('jobs', 'logs', 'timestamps', 'format')
|
75
78
|
if num_files_to_keep < 2:
|
76
79
|
raise ValueError("At least 2 files must be kept.")
|
77
80
|
if max_file_size < 1:
|
@@ -13,7 +13,7 @@ import os
|
|
13
13
|
import selectors
|
14
14
|
import traceback
|
15
15
|
|
16
|
-
from meerschaum.utils.typing import Optional
|
16
|
+
from meerschaum.utils.typing import Optional, Union
|
17
17
|
from meerschaum.utils.warnings import warn
|
18
18
|
|
19
19
|
|
@@ -23,9 +23,12 @@ class StdinFile(io.TextIOBase):
|
|
23
23
|
"""
|
24
24
|
def __init__(
|
25
25
|
self,
|
26
|
-
file_path: pathlib.Path,
|
26
|
+
file_path: Union[pathlib.Path, str],
|
27
27
|
lock_file_path: Optional[pathlib.Path] = None,
|
28
28
|
):
|
29
|
+
if isinstance(file_path, str):
|
30
|
+
file_path = pathlib.Path(file_path)
|
31
|
+
|
29
32
|
self.file_path = file_path
|
30
33
|
self.blocking_file_path = (
|
31
34
|
lock_file_path
|
@@ -108,3 +111,10 @@ class StdinFile(io.TextIOBase):
|
|
108
111
|
|
109
112
|
def is_open(self):
|
110
113
|
return self._file_handler is not None
|
114
|
+
|
115
|
+
|
116
|
+
def __str__(self) -> str:
|
117
|
+
return f"StdinFile('{self.file_path}')"
|
118
|
+
|
119
|
+
def __repr__(self) -> str:
|
120
|
+
return str(self)
|
@@ -10,9 +10,11 @@ from __future__ import annotations
|
|
10
10
|
import os, pathlib, shutil, json, datetime, threading, shlex
|
11
11
|
from meerschaum.utils.typing import SuccessTuple, List, Optional, Callable, Any, Dict
|
12
12
|
from meerschaum.config._paths import DAEMON_RESOURCES_PATH
|
13
|
+
from meerschaum.utils.daemon.StdinFile import StdinFile
|
13
14
|
from meerschaum.utils.daemon.Daemon import Daemon
|
14
15
|
from meerschaum.utils.daemon.RotatingFile import RotatingFile
|
15
16
|
from meerschaum.utils.daemon.FileDescriptorInterceptor import FileDescriptorInterceptor
|
17
|
+
from meerschaum.utils.daemon._names import get_new_daemon_name
|
16
18
|
|
17
19
|
|
18
20
|
def daemon_entry(sysargs: Optional[List[str]] = None) -> SuccessTuple:
|
@@ -12,12 +12,13 @@ from datetime import datetime, timezone
|
|
12
12
|
|
13
13
|
import meerschaum as mrsm
|
14
14
|
from meerschaum.utils.typing import List, Optional, Any, is_success_tuple, Dict
|
15
|
-
from meerschaum.
|
15
|
+
from meerschaum.jobs import (
|
16
16
|
Job,
|
17
17
|
get_jobs,
|
18
18
|
get_running_jobs,
|
19
19
|
get_stopped_jobs,
|
20
20
|
get_paused_jobs,
|
21
|
+
get_executor_keys_from_context,
|
21
22
|
)
|
22
23
|
from meerschaum.config import get_config
|
23
24
|
|
@@ -28,11 +29,17 @@ def pprint_jobs(
|
|
28
29
|
):
|
29
30
|
"""Pretty-print a list of Daemons."""
|
30
31
|
from meerschaum.utils.formatting import make_header
|
32
|
+
from meerschaum.utils.misc import items_str
|
31
33
|
|
32
34
|
running_jobs = get_running_jobs(jobs=jobs)
|
33
35
|
paused_jobs = get_paused_jobs(jobs=jobs)
|
34
36
|
stopped_jobs = get_stopped_jobs(jobs=jobs)
|
35
|
-
|
37
|
+
executor_keys_list = list(set(
|
38
|
+
[
|
39
|
+
job.executor_keys.replace('systemd:main', 'systemd')
|
40
|
+
for job in jobs.values()
|
41
|
+
]
|
42
|
+
)) if jobs else [get_executor_keys_from_context()]
|
36
43
|
|
37
44
|
def _nopretty_print():
|
38
45
|
from meerschaum.utils.misc import print_options
|
@@ -62,12 +69,20 @@ def pprint_jobs(
|
|
62
69
|
'rich.table', 'rich.text', 'rich.box', 'rich.json', 'rich.panel', 'rich.console',
|
63
70
|
)
|
64
71
|
table = rich_table.Table(
|
65
|
-
title=rich_text.Text(
|
72
|
+
title=rich_text.Text(
|
73
|
+
f"\nJobs on Executor"
|
74
|
+
+ ('s' if len(executor_keys_list) != 1 else '')
|
75
|
+
+ f" {items_str(executor_keys_list)}"
|
76
|
+
),
|
66
77
|
box=(rich_box.ROUNDED if UNICODE else rich_box.ASCII),
|
67
78
|
show_lines=True,
|
68
79
|
show_header=ANSI,
|
69
80
|
)
|
70
81
|
table.add_column("Name", justify='right', style=('magenta' if ANSI else ''))
|
82
|
+
table.add_column(
|
83
|
+
"Executor",
|
84
|
+
style=(get_config('shell', 'ansi', 'executor', 'rich', 'style') if ANSI else ''),
|
85
|
+
)
|
71
86
|
table.add_column("Command")
|
72
87
|
table.add_column("Status")
|
73
88
|
|
@@ -97,43 +112,55 @@ def pprint_jobs(
|
|
97
112
|
|
98
113
|
|
99
114
|
for name, job in running_jobs.items():
|
100
|
-
|
101
|
-
|
115
|
+
status_group = [
|
116
|
+
(
|
117
|
+
rich_text.Text(job.status, style=('green' if ANSI else ''))
|
118
|
+
if not job.is_blocking_on_stdin()
|
119
|
+
else rich_text.Text('waiting for input', style=('yellow' if ANSI else ''))
|
120
|
+
),
|
121
|
+
] + ([rich_text.Text(f"PID: {pid}")] if (pid := job.pid) else [])
|
102
122
|
|
103
|
-
|
104
|
-
rich_text.Text(
|
105
|
-
if not job.is_blocking_on_stdin()
|
106
|
-
else rich_text.Text('waiting for input', style=('yellow' if ANSI else ''))
|
107
|
-
)
|
123
|
+
if job.restart:
|
124
|
+
status_group.append(rich_text.Text('(restarts)'))
|
108
125
|
|
109
126
|
table.add_row(
|
110
127
|
job.name,
|
128
|
+
job.executor_keys.replace('systemd:main', 'systemd'),
|
111
129
|
job.label,
|
112
|
-
rich_console.Group(
|
130
|
+
rich_console.Group(*status_group),
|
113
131
|
)
|
114
132
|
|
115
133
|
for name, job in paused_jobs.items():
|
116
|
-
|
117
|
-
|
134
|
+
status_group = [
|
135
|
+
rich_text.Text(job.status, style=('yellow' if ANSI else '')),
|
136
|
+
]
|
137
|
+
if job.restart:
|
138
|
+
status_group.append(rich_text.Text('(restarts)'))
|
139
|
+
|
118
140
|
table.add_row(
|
119
141
|
job.name,
|
142
|
+
job.executor_keys.replace('systemd:main', 'systemd'),
|
120
143
|
job.label,
|
121
|
-
rich_console.Group(
|
122
|
-
rich_text.Text(job.status, style=('yellow' if ANSI else '')),
|
123
|
-
),
|
144
|
+
rich_console.Group(*status_group),
|
124
145
|
)
|
125
146
|
|
126
147
|
for name, job in stopped_jobs.items():
|
127
|
-
|
128
|
-
|
148
|
+
status_group = [
|
149
|
+
rich_text.Text(job.status, style=('red' if ANSI else '')),
|
150
|
+
]
|
151
|
+
if job.restart:
|
152
|
+
if job.stop_time is None:
|
153
|
+
status_group.append(rich_text.Text('(restarts)'))
|
154
|
+
else:
|
155
|
+
status_group.append(rich_text.Text('(start to resume restarts)'))
|
156
|
+
|
157
|
+
status_group.append(get_success_text(job))
|
129
158
|
|
130
159
|
table.add_row(
|
131
160
|
job.name,
|
161
|
+
job.executor_keys.replace('systemd:main', 'systemd'),
|
132
162
|
job.label,
|
133
|
-
rich_console.Group(
|
134
|
-
rich_text.Text(job.status, style=('red' if ANSI else '')),
|
135
|
-
get_success_text(job)
|
136
|
-
),
|
163
|
+
rich_console.Group(*status_group),
|
137
164
|
)
|
138
165
|
get_console().print(table)
|
139
166
|
|
@@ -146,9 +173,6 @@ def pprint_job(
|
|
146
173
|
nopretty: bool = False,
|
147
174
|
):
|
148
175
|
"""Pretty-print a single `Job`."""
|
149
|
-
if job.hidden:
|
150
|
-
return
|
151
|
-
|
152
176
|
from meerschaum.utils.warnings import info
|
153
177
|
if not nopretty:
|
154
178
|
info(f"Command for job '{job.name}':")
|
meerschaum/utils/misc.py
CHANGED
@@ -902,6 +902,7 @@ def get_connector_labels(
|
|
902
902
|
*types: str,
|
903
903
|
search_term: str = '',
|
904
904
|
ignore_exact_match = True,
|
905
|
+
_additional_options: Optional[List[str]] = None,
|
905
906
|
) -> List[str]:
|
906
907
|
"""
|
907
908
|
Read connector labels from the configuration dictionary.
|
@@ -941,12 +942,16 @@ def get_connector_labels(
|
|
941
942
|
continue
|
942
943
|
conns += [ f'{t}:{label}' for label in connectors.get(t, {}) if label != 'default' ]
|
943
944
|
|
945
|
+
if _additional_options:
|
946
|
+
conns += _additional_options
|
947
|
+
|
944
948
|
possibilities = [
|
945
|
-
c
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
949
|
+
c
|
950
|
+
for c in conns
|
951
|
+
if c.startswith(search_term)
|
952
|
+
and c != (
|
953
|
+
search_term if ignore_exact_match else ''
|
954
|
+
)
|
950
955
|
]
|
951
956
|
return sorted(possibilities)
|
952
957
|
|
@@ -1218,6 +1223,19 @@ def is_bcp_available() -> bool:
|
|
1218
1223
|
return has_bcp
|
1219
1224
|
|
1220
1225
|
|
1226
|
+
def is_systemd_available() -> bool:
|
1227
|
+
"""Check if running on systemd."""
|
1228
|
+
import subprocess
|
1229
|
+
try:
|
1230
|
+
has_systemctl = subprocess.call(
|
1231
|
+
['systemctl', '-h'],
|
1232
|
+
stdout=subprocess.DEVNULL,
|
1233
|
+
stderr=subprocess.STDOUT,
|
1234
|
+
) == 0
|
1235
|
+
except Exception:
|
1236
|
+
has_systemctl = False
|
1237
|
+
return has_systemctl
|
1238
|
+
|
1221
1239
|
def get_last_n_lines(file_name: str, N: int):
|
1222
1240
|
"""
|
1223
1241
|
https://thispointer.com/python-get-last-n-lines-of-a-text-file-like-tail-command/
|