meerschaum 2.3.0rc2__py3-none-any.whl → 2.3.0rc5__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/_internal/arguments/__init__.py +1 -1
- meerschaum/_internal/arguments/_parse_arguments.py +17 -12
- meerschaum/_internal/arguments/_parser.py +3 -6
- meerschaum/_internal/entry.py +42 -4
- meerschaum/_internal/shell/Shell.py +84 -72
- meerschaum/actions/delete.py +4 -0
- meerschaum/actions/show.py +5 -5
- meerschaum/actions/start.py +71 -1
- meerschaum/api/dash/callbacks/jobs.py +36 -44
- meerschaum/api/dash/jobs.py +24 -15
- meerschaum/api/routes/_actions.py +54 -6
- meerschaum/api/routes/_jobs.py +19 -1
- meerschaum/config/_jobs.py +1 -1
- meerschaum/config/_paths.py +1 -0
- meerschaum/config/_version.py +1 -1
- meerschaum/config/static/__init__.py +2 -0
- meerschaum/connectors/api/APIConnector.py +7 -1
- meerschaum/connectors/api/_actions.py +77 -1
- meerschaum/connectors/api/_jobs.py +13 -2
- meerschaum/connectors/api/_pipes.py +85 -84
- meerschaum/jobs/_Job.py +53 -12
- meerschaum/jobs/_SystemdExecutor.py +111 -30
- meerschaum/jobs/__init__.py +9 -2
- meerschaum/utils/daemon/Daemon.py +14 -0
- meerschaum/utils/daemon/StdinFile.py +1 -0
- meerschaum/utils/daemon/_names.py +15 -13
- {meerschaum-2.3.0rc2.dist-info → meerschaum-2.3.0rc5.dist-info}/METADATA +1 -1
- {meerschaum-2.3.0rc2.dist-info → meerschaum-2.3.0rc5.dist-info}/RECORD +34 -34
- {meerschaum-2.3.0rc2.dist-info → meerschaum-2.3.0rc5.dist-info}/LICENSE +0 -0
- {meerschaum-2.3.0rc2.dist-info → meerschaum-2.3.0rc5.dist-info}/NOTICE +0 -0
- {meerschaum-2.3.0rc2.dist-info → meerschaum-2.3.0rc5.dist-info}/WHEEL +0 -0
- {meerschaum-2.3.0rc2.dist-info → meerschaum-2.3.0rc5.dist-info}/entry_points.txt +0 -0
- {meerschaum-2.3.0rc2.dist-info → meerschaum-2.3.0rc5.dist-info}/top_level.txt +0 -0
- {meerschaum-2.3.0rc2.dist-info → meerschaum-2.3.0rc5.dist-info}/zip-safe +0 -0
@@ -24,13 +24,13 @@ from dash import Patch
|
|
24
24
|
html, dcc = import_html(check_update=CHECK_UPDATE), import_dcc(check_update=CHECK_UPDATE)
|
25
25
|
import dash_bootstrap_components as dbc
|
26
26
|
from meerschaum.api.dash.components import alert_from_success_tuple, build_cards_grid
|
27
|
-
from meerschaum.utils.daemon import Daemon
|
28
27
|
from dash.exceptions import PreventUpdate
|
29
28
|
from meerschaum.api.dash.jobs import (
|
30
29
|
build_manage_job_buttons_div_children,
|
31
30
|
build_status_children,
|
32
31
|
build_process_timestamps_children,
|
33
32
|
)
|
33
|
+
from meerschaum.jobs import Job
|
34
34
|
from meerschaum.api.dash.users import is_session_authenticated
|
35
35
|
|
36
36
|
@dash_app.callback(
|
@@ -53,15 +53,11 @@ def download_job_logs(n_clicks):
|
|
53
53
|
raise PreventUpdate
|
54
54
|
|
55
55
|
component_dict = json.loads(ctx[0]['prop_id'].split('.' + 'n_clicks')[0])
|
56
|
-
|
57
|
-
daemon = Daemon(daemon_id=daemon_id)
|
56
|
+
job_name = component_dict['index']
|
58
57
|
now = datetime.now(timezone.utc)
|
59
|
-
filename = (
|
60
|
-
daemon.rotating_log.file_path.name[:(-1 * len('.log'))]
|
61
|
-
+ '_' + str(int(now.timestamp())) + '.log'
|
62
|
-
)
|
58
|
+
filename = job_name + '_' + str(int(now.timestamp())) + '.log'
|
63
59
|
return {
|
64
|
-
'content':
|
60
|
+
'content': job.get_logs(),
|
65
61
|
'filename': filename,
|
66
62
|
}
|
67
63
|
|
@@ -73,12 +69,14 @@ def download_job_logs(n_clicks):
|
|
73
69
|
Output({'type': 'process-timestamps-div', 'index': MATCH}, 'children'),
|
74
70
|
Input({'type': 'manage-job-button', 'action': ALL, 'index': MATCH}, 'n_clicks'),
|
75
71
|
State('session-store', 'data'),
|
72
|
+
State({'type': 'job-label-p', 'index': MATCH}, 'children'),
|
76
73
|
prevent_initial_call = True,
|
77
74
|
)
|
78
75
|
def manage_job_button_click(
|
79
|
-
|
80
|
-
|
81
|
-
|
76
|
+
n_clicks: Optional[int] = None,
|
77
|
+
session_data: Optional[Dict[str, Any]] = None,
|
78
|
+
job_label: Optional[str] = None,
|
79
|
+
):
|
82
80
|
"""
|
83
81
|
Start, stop, pause, or delete the given job.
|
84
82
|
"""
|
@@ -102,20 +100,20 @@ def manage_job_button_click(
|
|
102
100
|
raise PreventUpdate
|
103
101
|
|
104
102
|
component_dict = json.loads(ctx[0]['prop_id'].split('.' + 'n_clicks')[0])
|
105
|
-
|
103
|
+
job_name = component_dict['index']
|
106
104
|
manage_job_action = component_dict['action']
|
107
105
|
try:
|
108
|
-
|
106
|
+
job = Job(job_name, job_label.replace('\n', ' ') if job_label else None)
|
109
107
|
except Exception as e:
|
110
|
-
|
111
|
-
if
|
108
|
+
job = None
|
109
|
+
if job is None:
|
112
110
|
raise PreventUpdate
|
113
111
|
|
114
112
|
manage_functions = {
|
115
|
-
'start':
|
116
|
-
'stop':
|
117
|
-
'pause':
|
118
|
-
'delete':
|
113
|
+
'start': job.start,
|
114
|
+
'stop': job.stop,
|
115
|
+
'pause': job.pause,
|
116
|
+
'delete': job.delete,
|
119
117
|
}
|
120
118
|
if manage_job_action not in manage_functions:
|
121
119
|
return (
|
@@ -125,7 +123,7 @@ def manage_job_button_click(
|
|
125
123
|
dash.no_update,
|
126
124
|
)
|
127
125
|
|
128
|
-
old_status =
|
126
|
+
old_status = job.status
|
129
127
|
try:
|
130
128
|
success, msg = manage_functions[manage_job_action]()
|
131
129
|
except Exception as e:
|
@@ -136,15 +134,15 @@ def manage_job_button_click(
|
|
136
134
|
check_interval_seconds = 0.01
|
137
135
|
begin = time.perf_counter()
|
138
136
|
while (time.perf_counter() - begin) < timeout_seconds:
|
139
|
-
if
|
137
|
+
if job.status != old_status:
|
140
138
|
break
|
141
139
|
time.sleep(check_interval_seconds)
|
142
140
|
|
143
141
|
return (
|
144
142
|
alert_from_success_tuple((success, msg)),
|
145
|
-
build_manage_job_buttons_div_children(
|
146
|
-
build_status_children(
|
147
|
-
build_process_timestamps_children(
|
143
|
+
build_manage_job_buttons_div_children(job),
|
144
|
+
build_status_children(job),
|
145
|
+
build_process_timestamps_children(job),
|
148
146
|
)
|
149
147
|
|
150
148
|
dash_app.clientside_callback(
|
@@ -165,7 +163,7 @@ dash_app.clientside_callback(
|
|
165
163
|
}
|
166
164
|
|
167
165
|
const triggered_id = dash_clientside.callback_context.triggered_id;
|
168
|
-
const
|
166
|
+
const job_name = triggered_id["index"];
|
169
167
|
|
170
168
|
iframe = document.getElementById('webterm-iframe');
|
171
169
|
if (!iframe){ return dash_clientside.no_update; }
|
@@ -174,7 +172,7 @@ dash_app.clientside_callback(
|
|
174
172
|
{
|
175
173
|
action: "show",
|
176
174
|
subaction: "logs",
|
177
|
-
subaction_text:
|
175
|
+
subaction_text: job_name,
|
178
176
|
},
|
179
177
|
url
|
180
178
|
);
|
@@ -197,44 +195,38 @@ dash_app.clientside_callback(
|
|
197
195
|
prevent_initial_call = True,
|
198
196
|
)
|
199
197
|
def refresh_jobs_on_interval(
|
200
|
-
|
201
|
-
|
202
|
-
|
198
|
+
n_intervals: Optional[int] = None,
|
199
|
+
session_data: Optional[Dict[str, Any]] = None,
|
200
|
+
):
|
203
201
|
"""
|
204
202
|
When the jobs refresh interval fires, rebuild the jobs' onscreen components.
|
205
203
|
"""
|
206
204
|
session_id = session_data.get('session-id', None)
|
207
205
|
is_authenticated = is_session_authenticated(session_id)
|
208
206
|
|
209
|
-
|
207
|
+
job_names = [
|
210
208
|
component_dict['id']['index']
|
211
209
|
for component_dict in dash.callback_context.outputs_grouping[0]
|
212
210
|
]
|
213
211
|
|
214
|
-
### NOTE: The
|
215
|
-
|
216
|
-
for daemon_id in daemon_ids:
|
217
|
-
try:
|
218
|
-
daemon = Daemon(daemon_id=daemon_id)
|
219
|
-
except Exception as e:
|
220
|
-
daemon = None
|
221
|
-
daemons.append(daemon)
|
212
|
+
### NOTE: The job may have been deleted, but the card may still exist.
|
213
|
+
jobs = [Job(name) for name in job_names]
|
222
214
|
|
223
215
|
return (
|
224
216
|
[
|
225
217
|
(
|
226
|
-
build_manage_job_buttons_div_children(
|
218
|
+
build_manage_job_buttons_div_children(job)
|
227
219
|
if is_authenticated
|
228
220
|
else []
|
229
221
|
)
|
230
|
-
for
|
222
|
+
for job in jobs
|
231
223
|
],
|
232
224
|
[
|
233
|
-
build_status_children(
|
234
|
-
for
|
225
|
+
build_status_children(job)
|
226
|
+
for job in jobs
|
235
227
|
],
|
236
228
|
[
|
237
|
-
build_process_timestamps_children(
|
238
|
-
for
|
229
|
+
build_process_timestamps_children(job)
|
230
|
+
for job in jobs
|
239
231
|
],
|
240
232
|
)
|
meerschaum/api/dash/jobs.py
CHANGED
@@ -15,12 +15,12 @@ from meerschaum.api.dash.users import is_session_authenticated
|
|
15
15
|
from meerschaum.api import CHECK_UPDATE
|
16
16
|
dbc = attempt_import('dash_bootstrap_components', lazy=False, check_update=CHECK_UPDATE)
|
17
17
|
html, dcc = import_html(), import_dcc()
|
18
|
-
dateutil_parser = attempt_import('dateutil.parser', check_update=CHECK_UPDATE)
|
19
18
|
from meerschaum.jobs import (
|
20
19
|
get_jobs,
|
21
20
|
get_running_jobs,
|
22
21
|
get_paused_jobs,
|
23
22
|
get_stopped_jobs,
|
23
|
+
get_executor_keys_from_context,
|
24
24
|
Job,
|
25
25
|
)
|
26
26
|
from meerschaum.config import get_config
|
@@ -33,20 +33,21 @@ STATUS_EMOJI: Dict[str, str] = {
|
|
33
33
|
'dne': get_config('formatting', 'emoji', 'failure')
|
34
34
|
}
|
35
35
|
|
36
|
+
EXECUTOR_KEYS: str = get_executor_keys_from_context()
|
37
|
+
|
36
38
|
def get_jobs_cards(state: WebState):
|
37
39
|
"""
|
38
40
|
Build cards and alerts lists for jobs.
|
39
41
|
"""
|
40
|
-
jobs = get_jobs(include_hidden=False)
|
42
|
+
jobs = get_jobs(executor_keys=EXECUTOR_KEYS, include_hidden=False)
|
41
43
|
session_id = state['session-store.data'].get('session-id', None)
|
42
44
|
is_authenticated = is_session_authenticated(session_id)
|
43
45
|
|
44
46
|
cards = []
|
45
47
|
|
46
48
|
for name, job in jobs.items():
|
47
|
-
d = job.daemon
|
48
49
|
footer_children = html.Div(
|
49
|
-
build_process_timestamps_children(
|
50
|
+
build_process_timestamps_children(job),
|
50
51
|
id = {'type': 'process-timestamps-div', 'index': name},
|
51
52
|
)
|
52
53
|
follow_logs_button = dbc.DropdownMenuItem(
|
@@ -62,7 +63,7 @@ def get_jobs_cards(state: WebState):
|
|
62
63
|
)
|
63
64
|
header_children = [
|
64
65
|
html.Div(
|
65
|
-
build_status_children(
|
66
|
+
build_status_children(job),
|
66
67
|
id={'type': 'manage-job-status-div', 'index': name},
|
67
68
|
style={'float': 'left'},
|
68
69
|
),
|
@@ -83,15 +84,16 @@ def get_jobs_cards(state: WebState):
|
|
83
84
|
html.H4(html.B(name), className="card-title"),
|
84
85
|
html.Div(
|
85
86
|
html.P(
|
86
|
-
|
87
|
+
job.label,
|
87
88
|
className="card-text job-card-text",
|
88
89
|
style={"word-wrap": "break-word"},
|
90
|
+
id={'type': 'job-label-p', 'index': name},
|
89
91
|
),
|
90
92
|
style={"white-space": "pre-wrap"},
|
91
93
|
),
|
92
94
|
html.Div(
|
93
95
|
(
|
94
|
-
build_manage_job_buttons_div_children(
|
96
|
+
build_manage_job_buttons_div_children(job)
|
95
97
|
if is_authenticated
|
96
98
|
else []
|
97
99
|
),
|
@@ -179,13 +181,14 @@ def build_manage_job_buttons(job: Job):
|
|
179
181
|
},
|
180
182
|
)
|
181
183
|
buttons = []
|
182
|
-
|
184
|
+
status = job.status
|
185
|
+
if status in ('stopped', 'paused'):
|
183
186
|
buttons.append(start_button)
|
184
|
-
if
|
187
|
+
if status == 'stopped':
|
185
188
|
buttons.append(delete_button)
|
186
|
-
if
|
189
|
+
if status in ('running',):
|
187
190
|
buttons.append(pause_button)
|
188
|
-
if
|
191
|
+
if status in ('running', 'paused'):
|
189
192
|
buttons.append(stop_button)
|
190
193
|
|
191
194
|
return buttons
|
@@ -193,7 +196,7 @@ def build_manage_job_buttons(job: Job):
|
|
193
196
|
|
194
197
|
def build_status_children(job: Job) -> List[html.P]:
|
195
198
|
"""
|
196
|
-
Return the status HTML component for this
|
199
|
+
Return the status HTML component for this Job.
|
197
200
|
"""
|
198
201
|
if job is None:
|
199
202
|
return STATUS_EMOJI['dne']
|
@@ -217,10 +220,16 @@ def build_process_timestamps_children(job: Job) -> List[dbc.Row]:
|
|
217
220
|
return []
|
218
221
|
|
219
222
|
children = []
|
220
|
-
for timestamp_key,
|
221
|
-
|
223
|
+
for timestamp_key, timestamp in sorted_dict(
|
224
|
+
{
|
225
|
+
'began': job.began,
|
226
|
+
'paused': job.paused,
|
227
|
+
'ended': job.ended,
|
228
|
+
}
|
222
229
|
).items():
|
223
|
-
timestamp
|
230
|
+
if timestamp is None:
|
231
|
+
continue
|
232
|
+
|
224
233
|
timestamp_str = timestamp.strftime('%Y-%m-%d %H:%M UTC')
|
225
234
|
children.append(
|
226
235
|
dbc.Row(
|
@@ -18,7 +18,7 @@ from fastapi import WebSocket, WebSocketDisconnect
|
|
18
18
|
from meerschaum.utils.misc import generate_password
|
19
19
|
from meerschaum.jobs import Job
|
20
20
|
from meerschaum.utils.warnings import warn
|
21
|
-
from meerschaum.utils.typing import SuccessTuple, Union, List, Dict
|
21
|
+
from meerschaum.utils.typing import SuccessTuple, Union, List, Dict, Any
|
22
22
|
from meerschaum.api import (
|
23
23
|
fastapi, app, endpoints, get_api_connector, debug, manager, private, no_auth
|
24
24
|
)
|
@@ -26,6 +26,7 @@ from meerschaum.actions import actions
|
|
26
26
|
import meerschaum.core
|
27
27
|
from meerschaum.config import get_config
|
28
28
|
from meerschaum._internal.arguments._parse_arguments import parse_dict_to_sysargs, parse_arguments
|
29
|
+
from meerschaum.api.routes._jobs import clean_sysargs
|
29
30
|
|
30
31
|
actions_endpoint = endpoints['actions']
|
31
32
|
|
@@ -115,11 +116,11 @@ async def do_action_websocket(websocket: WebSocket):
|
|
115
116
|
if not auth_success:
|
116
117
|
await websocket.close()
|
117
118
|
|
118
|
-
sysargs = await websocket.receive_json()
|
119
|
-
kwargs = parse_arguments(sysargs)
|
120
|
-
_ = kwargs.pop('executor_keys', None)
|
121
|
-
_ = kwargs.pop('shell', None)
|
122
|
-
sysargs = parse_dict_to_sysargs(kwargs)
|
119
|
+
sysargs = clean_sysargs(await websocket.receive_json())
|
120
|
+
# kwargs = parse_arguments(sysargs)
|
121
|
+
# _ = kwargs.pop('executor_keys', None)
|
122
|
+
# _ = kwargs.pop('shell', None)
|
123
|
+
# sysargs = parse_dict_to_sysargs(kwargs)
|
123
124
|
|
124
125
|
job = Job(
|
125
126
|
job_name,
|
@@ -152,3 +153,50 @@ async def do_action_websocket(websocket: WebSocket):
|
|
152
153
|
job.delete()
|
153
154
|
_ = _temp_jobs.pop(job_name, None)
|
154
155
|
stop_event.set()
|
156
|
+
|
157
|
+
|
158
|
+
@app.post(actions_endpoint + "/{action}", tags=['Actions'])
|
159
|
+
def do_action_legacy(
|
160
|
+
action: str,
|
161
|
+
keywords: Dict[str, Any] = fastapi.Body(...),
|
162
|
+
curr_user = (
|
163
|
+
fastapi.Depends(manager) if not no_auth else None
|
164
|
+
),
|
165
|
+
) -> SuccessTuple:
|
166
|
+
"""
|
167
|
+
Perform a Meerschaum action (if permissions allow).
|
168
|
+
|
169
|
+
Parameters
|
170
|
+
----------
|
171
|
+
action: str
|
172
|
+
The action to perform.
|
173
|
+
|
174
|
+
keywords: Dict[str, Any]
|
175
|
+
The keywords dictionary to pass to the action.
|
176
|
+
|
177
|
+
Returns
|
178
|
+
-------
|
179
|
+
A `SuccessTuple`.
|
180
|
+
"""
|
181
|
+
if curr_user is not None and curr_user.type != 'admin':
|
182
|
+
from meerschaum.config import get_config
|
183
|
+
allow_non_admin = get_config(
|
184
|
+
'system', 'api', 'permissions', 'actions', 'non_admin', patch=True
|
185
|
+
)
|
186
|
+
if not allow_non_admin:
|
187
|
+
return False, (
|
188
|
+
"The administrator for this server has not allowed users to perform actions.\n\n"
|
189
|
+
+ "Please contact the system administrator, or if you are running this server, "
|
190
|
+
+ "open the configuration file with `edit config system` "
|
191
|
+
+ "and search for 'permissions'. "
|
192
|
+
+ "\nUnder the keys 'api:permissions:actions', "
|
193
|
+
+ "you can allow non-admin users to perform actions."
|
194
|
+
)
|
195
|
+
|
196
|
+
if action not in actions:
|
197
|
+
return False, f"Invalid action '{action}'."
|
198
|
+
|
199
|
+
keywords['mrsm_instance'] = keywords.get('mrsm_instance', str(get_api_connector()))
|
200
|
+
_debug = keywords.get('debug', debug)
|
201
|
+
keywords.pop('debug', None)
|
202
|
+
return actions[action](debug=_debug, **keywords)
|
meerschaum/api/routes/_jobs.py
CHANGED
@@ -107,6 +107,24 @@ def get_job(
|
|
107
107
|
}
|
108
108
|
|
109
109
|
|
110
|
+
def clean_sysargs(sysargs: List[str]) -> List[str]:
|
111
|
+
"""
|
112
|
+
Remove the executor flag or leading `api {label}` action.
|
113
|
+
"""
|
114
|
+
clean_sysargs = []
|
115
|
+
executor_flag = False
|
116
|
+
for arg in sysargs:
|
117
|
+
if arg in ('-e', '--executor', 'api'):
|
118
|
+
executor_flag = True
|
119
|
+
continue
|
120
|
+
if executor_flag:
|
121
|
+
executor_flag = False
|
122
|
+
continue
|
123
|
+
|
124
|
+
clean_sysargs.append(arg)
|
125
|
+
return clean_sysargs
|
126
|
+
|
127
|
+
|
110
128
|
@app.post(endpoints['jobs'] + '/{name}', tags=['Jobs'])
|
111
129
|
def create_job(
|
112
130
|
name: str,
|
@@ -118,7 +136,7 @@ def create_job(
|
|
118
136
|
"""
|
119
137
|
Create and start a new job.
|
120
138
|
"""
|
121
|
-
job = Job(name, sysargs, executor_keys=EXECUTOR_KEYS)
|
139
|
+
job = Job(name, clean_sysargs(sysargs), executor_keys=EXECUTOR_KEYS)
|
122
140
|
if job.exists():
|
123
141
|
raise fastapi.HTTPException(
|
124
142
|
status_code=409,
|
meerschaum/config/_jobs.py
CHANGED
meerschaum/config/_paths.py
CHANGED
@@ -185,6 +185,7 @@ paths = {
|
|
185
185
|
'SYSTEMD_RESOURCES_PATH' : ('{DOT_CONFIG_DIR_PATH}', 'systemd'),
|
186
186
|
'SYSTEMD_USER_RESOURCES_PATH' : ('{SYSTEMD_RESOURCES_PATH}', 'user'),
|
187
187
|
'SYSTEMD_ROOT_RESOURCES_PATH' : ('{ROOT_DIR_PATH}', 'systemd'),
|
188
|
+
'SYSTEMD_JOBS_RESOURCES_PATH' : ('{SYSTEMD_ROOT_RESOURCES_PATH}', 'services'),
|
188
189
|
'SYSTEMD_LOGS_RESOURCES_PATH' : ('{SYSTEMD_ROOT_RESOURCES_PATH}', 'logs'),
|
189
190
|
}
|
190
191
|
|
meerschaum/config/_version.py
CHANGED
@@ -33,7 +33,12 @@ class APIConnector(Connector):
|
|
33
33
|
delete,
|
34
34
|
wget,
|
35
35
|
)
|
36
|
-
from ._actions import
|
36
|
+
from ._actions import (
|
37
|
+
get_actions,
|
38
|
+
do_action,
|
39
|
+
do_action_async,
|
40
|
+
do_action_legacy,
|
41
|
+
)
|
37
42
|
from ._misc import get_mrsm_version, get_chaining_status
|
38
43
|
from ._pipes import (
|
39
44
|
register_pipe,
|
@@ -90,6 +95,7 @@ class APIConnector(Connector):
|
|
90
95
|
get_job_is_blocking_on_stdin,
|
91
96
|
get_job_began,
|
92
97
|
get_job_ended,
|
98
|
+
get_job_paused,
|
93
99
|
get_job_status,
|
94
100
|
)
|
95
101
|
|
@@ -13,7 +13,7 @@ import asyncio
|
|
13
13
|
from functools import partial
|
14
14
|
|
15
15
|
import meerschaum as mrsm
|
16
|
-
from meerschaum.utils.typing import SuccessTuple, List, Callable
|
16
|
+
from meerschaum.utils.typing import SuccessTuple, List, Callable, Optional
|
17
17
|
from meerschaum.config.static import STATIC_CONFIG
|
18
18
|
|
19
19
|
ACTIONS_ENDPOINT: str = STATIC_CONFIG['api']['endpoints']['actions']
|
@@ -74,3 +74,79 @@ async def do_action_async(
|
|
74
74
|
break
|
75
75
|
|
76
76
|
return True, "Success"
|
77
|
+
|
78
|
+
|
79
|
+
def do_action_legacy(
|
80
|
+
self,
|
81
|
+
action: Optional[List[str]] = None,
|
82
|
+
sysargs: Optional[List[str]] = None,
|
83
|
+
debug: bool = False,
|
84
|
+
**kw
|
85
|
+
) -> SuccessTuple:
|
86
|
+
"""
|
87
|
+
NOTE: This method is deprecated.
|
88
|
+
Please use `do_action()` or `do_action_async()`.
|
89
|
+
|
90
|
+
Execute a Meerschaum action remotely.
|
91
|
+
|
92
|
+
If `sysargs` are provided, parse those instead.
|
93
|
+
Otherwise infer everything from keyword arguments.
|
94
|
+
|
95
|
+
Examples
|
96
|
+
--------
|
97
|
+
>>> conn = mrsm.get_connector('api:main')
|
98
|
+
>>> conn.do_action(['show', 'pipes'])
|
99
|
+
(True, "Success")
|
100
|
+
>>> conn.do_action(['show', 'arguments'], name='test')
|
101
|
+
(True, "Success")
|
102
|
+
"""
|
103
|
+
import sys, json
|
104
|
+
from meerschaum.utils.debug import dprint
|
105
|
+
from meerschaum.config.static import STATIC_CONFIG
|
106
|
+
from meerschaum.utils.misc import json_serialize_datetime
|
107
|
+
if action is None:
|
108
|
+
action = []
|
109
|
+
|
110
|
+
if sysargs is not None and action and action[0] == '':
|
111
|
+
from meerschaum._internal.arguments import parse_arguments
|
112
|
+
if debug:
|
113
|
+
dprint(f"Parsing sysargs:\n{sysargs}")
|
114
|
+
json_dict = parse_arguments(sysargs)
|
115
|
+
else:
|
116
|
+
json_dict = kw
|
117
|
+
json_dict['action'] = action
|
118
|
+
if 'noask' not in kw:
|
119
|
+
json_dict['noask'] = True
|
120
|
+
if 'yes' not in kw:
|
121
|
+
json_dict['yes'] = True
|
122
|
+
if debug:
|
123
|
+
json_dict['debug'] = debug
|
124
|
+
|
125
|
+
root_action = json_dict['action'][0]
|
126
|
+
del json_dict['action'][0]
|
127
|
+
r_url = f"{STATIC_CONFIG['api']['endpoints']['actions']}/{root_action}"
|
128
|
+
|
129
|
+
if debug:
|
130
|
+
from meerschaum.utils.formatting import pprint
|
131
|
+
dprint(f"Sending data to '{self.url + r_url}':")
|
132
|
+
pprint(json_dict, stream=sys.stderr)
|
133
|
+
|
134
|
+
response = self.post(
|
135
|
+
r_url,
|
136
|
+
data = json.dumps(json_dict, default=json_serialize_datetime),
|
137
|
+
debug = debug,
|
138
|
+
)
|
139
|
+
try:
|
140
|
+
response_list = json.loads(response.text)
|
141
|
+
if isinstance(response_list, dict) and 'detail' in response_list:
|
142
|
+
return False, response_list['detail']
|
143
|
+
except Exception as e:
|
144
|
+
print(f"Invalid response: {response}")
|
145
|
+
print(e)
|
146
|
+
return False, response.text
|
147
|
+
if debug:
|
148
|
+
dprint(response)
|
149
|
+
try:
|
150
|
+
return response_list[0], response_list[1]
|
151
|
+
except Exception as e:
|
152
|
+
return False, f"Failed to parse result from action '{root_action}'"
|
@@ -112,7 +112,7 @@ def get_job_status(self, name: str, debug: bool = False) -> str:
|
|
112
112
|
metadata = self.get_job_metadata(name, debug=debug)
|
113
113
|
return metadata.get('status', 'stopped')
|
114
114
|
|
115
|
-
def get_job_began(self, name: str, debug: bool = False) -> str:
|
115
|
+
def get_job_began(self, name: str, debug: bool = False) -> Union[str, None]:
|
116
116
|
"""
|
117
117
|
Return a job's `began` timestamp, if it exists.
|
118
118
|
"""
|
@@ -123,7 +123,7 @@ def get_job_began(self, name: str, debug: bool = False) -> str:
|
|
123
123
|
|
124
124
|
return began_str
|
125
125
|
|
126
|
-
def get_job_ended(self, name: str, debug: bool = False) -> str:
|
126
|
+
def get_job_ended(self, name: str, debug: bool = False) -> Union[str, None]:
|
127
127
|
"""
|
128
128
|
Return a job's `ended` timestamp, if it exists.
|
129
129
|
"""
|
@@ -134,6 +134,17 @@ def get_job_ended(self, name: str, debug: bool = False) -> str:
|
|
134
134
|
|
135
135
|
return ended_str
|
136
136
|
|
137
|
+
def get_job_paused(self, name: str, debug: bool = False) -> Union[str, None]:
|
138
|
+
"""
|
139
|
+
Return a job's `paused` timestamp, if it exists.
|
140
|
+
"""
|
141
|
+
properties = self.get_job_properties(name, debug=debug)
|
142
|
+
paused_str = properties.get('daemon', {}).get('paused', None)
|
143
|
+
if paused_str is None:
|
144
|
+
return None
|
145
|
+
|
146
|
+
return paused_str
|
147
|
+
|
137
148
|
def get_job_exists(self, name: str, debug: bool = False) -> bool:
|
138
149
|
"""
|
139
150
|
Return whether a job exists.
|