mantatech-sdk 0.5b0.dev65__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.
- manta/__init__.light.py +22 -0
- manta/__init__.py +83 -0
- manta/__main__.py +21 -0
- manta/apis/__init__.py +7 -0
- manta/apis/async_user_api.py +6458 -0
- manta/apis/graph.py +498 -0
- manta/apis/module.py +316 -0
- manta/apis/results.py +251 -0
- manta/apis/swarm.py +206 -0
- manta/apis/user_api.py +1016 -0
- manta/cli/__init__.py +1 -0
- manta/cli/commands/__init__.py +1 -0
- manta/cli/commands/base_handler.py +229 -0
- manta/cli/commands/doc.py +192 -0
- manta/cli/commands/install.py +346 -0
- manta/cli/commands/sdk.py +9 -0
- manta/cli/commands/sdk_cluster.py +211 -0
- manta/cli/commands/sdk_config.py +347 -0
- manta/cli/commands/sdk_globals.py +280 -0
- manta/cli/commands/sdk_logs.py +174 -0
- manta/cli/commands/sdk_main.py +167 -0
- manta/cli/commands/sdk_module.py +516 -0
- manta/cli/commands/sdk_nodes.py +168 -0
- manta/cli/commands/sdk_original.py +3873 -0
- manta/cli/commands/sdk_results.py +265 -0
- manta/cli/commands/sdk_swarm.py +454 -0
- manta/cli/commands/sdk_user.py +234 -0
- manta/cli/commands/status.py +292 -0
- manta/cli/component_detector.py +112 -0
- manta/cli/config_manager.py +445 -0
- manta/cli/main.py +265 -0
- manta/cli/utils/__init__.py +27 -0
- manta/cli/utils/converters.py +140 -0
- manta/clients/cluster_management_client.py +486 -0
- manta/clients/local_client.py +149 -0
- manta/clients/module_management_client.py +217 -0
- manta/clients/swarm_management_client.py +562 -0
- manta/clients/user_management_client.py +395 -0
- manta/clients/world_client.py +195 -0
- manta/light/__init__.py +31 -0
- manta/light/globals.py +245 -0
- manta/light/local.py +407 -0
- manta/light/logging_config.py +39 -0
- manta/light/path.py +116 -0
- manta/light/results.py +236 -0
- manta/light/task.py +100 -0
- manta/light/utils.py +217 -0
- manta/light/world.py +177 -0
- mantatech_sdk-0.5b0.dev65.dist-info/METADATA +1039 -0
- mantatech_sdk-0.5b0.dev65.dist-info/RECORD +54 -0
- mantatech_sdk-0.5b0.dev65.dist-info/WHEEL +5 -0
- mantatech_sdk-0.5b0.dev65.dist-info/entry_points.txt +2 -0
- mantatech_sdk-0.5b0.dev65.dist-info/licenses/LICENSE +683 -0
- mantatech_sdk-0.5b0.dev65.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
"""SDK swarm management and simulation commands."""
|
|
2
|
+
|
|
3
|
+
import signal
|
|
4
|
+
import time
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.prompt import Confirm
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
|
|
11
|
+
from .base_handler import BaseSDKHandler
|
|
12
|
+
from ..utils.converters import (
|
|
13
|
+
enum_to_string,
|
|
14
|
+
timestamp_to_string,
|
|
15
|
+
SWARM_STATUS_MAP,
|
|
16
|
+
TASK_STATUS_MAP,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SDKSwarmHandler(BaseSDKHandler):
|
|
21
|
+
"""Handle swarm-related SDK commands including simulations."""
|
|
22
|
+
|
|
23
|
+
def add_subparsers(self, parent_parser):
|
|
24
|
+
"""Add swarm command subparsers."""
|
|
25
|
+
self.parser = parent_parser # Store parser reference
|
|
26
|
+
subparsers = parent_parser.add_subparsers(
|
|
27
|
+
dest="swarm_command", help="Swarm commands"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# List swarms command
|
|
31
|
+
list_parser = subparsers.add_parser("list", help="List user swarms")
|
|
32
|
+
list_parser.add_argument("--cluster-id", help="Filter by cluster ID")
|
|
33
|
+
|
|
34
|
+
# List swarm IDs command
|
|
35
|
+
list_ids_parser = subparsers.add_parser("list-ids", help="List swarm IDs only")
|
|
36
|
+
|
|
37
|
+
# Show swarm command
|
|
38
|
+
show_parser = subparsers.add_parser(
|
|
39
|
+
"show", help="Show detailed swarm information"
|
|
40
|
+
)
|
|
41
|
+
show_parser.add_argument("swarm_id", help="Swarm ID to show")
|
|
42
|
+
|
|
43
|
+
# Show swarm tasks command
|
|
44
|
+
tasks_parser = subparsers.add_parser("tasks", help="Show swarm tasks")
|
|
45
|
+
tasks_parser.add_argument("swarm_id", help="Swarm ID to show tasks for")
|
|
46
|
+
|
|
47
|
+
# Start swarm command
|
|
48
|
+
start_parser = subparsers.add_parser("start", help="Start a swarm")
|
|
49
|
+
start_parser.add_argument("swarm_id", help="Swarm ID to start")
|
|
50
|
+
|
|
51
|
+
# Stop swarm command
|
|
52
|
+
stop_parser = subparsers.add_parser("stop", help="Stop a swarm")
|
|
53
|
+
stop_parser.add_argument("swarm_id", help="Swarm ID to stop")
|
|
54
|
+
stop_parser.add_argument(
|
|
55
|
+
"--force", action="store_true", help="Force stop without confirmation"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Delete/Remove swarm command
|
|
59
|
+
delete_parser = subparsers.add_parser("remove", help="Delete a swarm")
|
|
60
|
+
delete_parser.add_argument("swarm_id", help="Swarm ID to delete")
|
|
61
|
+
delete_parser.add_argument(
|
|
62
|
+
"--force", action="store_true", help="Skip confirmation"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Monitor swarm command
|
|
66
|
+
monitor_parser = subparsers.add_parser(
|
|
67
|
+
"monitor", help="Monitor swarm execution"
|
|
68
|
+
)
|
|
69
|
+
monitor_parser.add_argument("swarm_id", help="Swarm ID to monitor")
|
|
70
|
+
|
|
71
|
+
# Add profile override to all subcommands
|
|
72
|
+
for parser in [
|
|
73
|
+
list_parser,
|
|
74
|
+
list_ids_parser,
|
|
75
|
+
show_parser,
|
|
76
|
+
tasks_parser,
|
|
77
|
+
start_parser,
|
|
78
|
+
stop_parser,
|
|
79
|
+
delete_parser,
|
|
80
|
+
monitor_parser,
|
|
81
|
+
]:
|
|
82
|
+
parser.add_argument("--profile", help="Use specific profile")
|
|
83
|
+
|
|
84
|
+
def handle(self, args) -> int:
|
|
85
|
+
"""Handle swarm commands."""
|
|
86
|
+
# Handle both swarm_command and simulation_command (for backwards compatibility)
|
|
87
|
+
command = getattr(args, "swarm_command", None) or getattr(
|
|
88
|
+
args, "simulation_command", None
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if not command:
|
|
92
|
+
if hasattr(self, "parser"):
|
|
93
|
+
self.parser.print_help()
|
|
94
|
+
return 0 # Return 0 for help display
|
|
95
|
+
|
|
96
|
+
if command == "list":
|
|
97
|
+
return self.list_swarms(getattr(args, "cluster_id", None))
|
|
98
|
+
elif command == "list-ids":
|
|
99
|
+
return self.list_swarm_ids()
|
|
100
|
+
elif command == "show":
|
|
101
|
+
return self.show_swarm(args.swarm_id)
|
|
102
|
+
elif command == "tasks":
|
|
103
|
+
return self.show_swarm_tasks(args.swarm_id)
|
|
104
|
+
elif command == "start":
|
|
105
|
+
return self.start_swarm(args.swarm_id)
|
|
106
|
+
elif command == "stop":
|
|
107
|
+
return self.stop_swarm(args.swarm_id, getattr(args, "force", False))
|
|
108
|
+
elif command in ["delete", "remove"]:
|
|
109
|
+
return self.delete_swarm(args.swarm_id, getattr(args, "force", False))
|
|
110
|
+
elif command == "monitor":
|
|
111
|
+
return self.monitor_swarm(args.swarm_id)
|
|
112
|
+
else:
|
|
113
|
+
self.print_error(f"Unknown swarm command: {command}")
|
|
114
|
+
return 1
|
|
115
|
+
|
|
116
|
+
def list_swarm_ids(self) -> int:
|
|
117
|
+
"""List swarm IDs only."""
|
|
118
|
+
api = self._get_user_api()
|
|
119
|
+
if not api:
|
|
120
|
+
return 1
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
|
|
124
|
+
async def run():
|
|
125
|
+
return await api.list_swarm_ids()
|
|
126
|
+
|
|
127
|
+
swarm_ids = self._run_with_progress(run, "Fetching swarm IDs...")
|
|
128
|
+
|
|
129
|
+
if swarm_ids is None:
|
|
130
|
+
return 1
|
|
131
|
+
|
|
132
|
+
if not swarm_ids:
|
|
133
|
+
self.print("No swarms found.")
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
self.print(f"Found {len(swarm_ids)} swarms:")
|
|
137
|
+
for swarm_id in swarm_ids:
|
|
138
|
+
self.print(f" {swarm_id}")
|
|
139
|
+
|
|
140
|
+
return 0
|
|
141
|
+
|
|
142
|
+
except Exception as e:
|
|
143
|
+
self.print_error(f"Failed to list swarm IDs: {e}")
|
|
144
|
+
return 1
|
|
145
|
+
|
|
146
|
+
def list_swarms(self, cluster_id: Optional[str] = None) -> int:
|
|
147
|
+
"""List user swarms, optionally filtered by cluster."""
|
|
148
|
+
api = self._get_user_api()
|
|
149
|
+
if not api:
|
|
150
|
+
return 1
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
|
|
154
|
+
async def run():
|
|
155
|
+
# stream_and_fetch_swarms doesn't have cluster_id parameter
|
|
156
|
+
# Get all swarms and filter by cluster if needed
|
|
157
|
+
swarms = await api.stream_and_fetch_swarms()
|
|
158
|
+
if cluster_id:
|
|
159
|
+
# Filter swarms by cluster_id
|
|
160
|
+
swarms = [s for s in swarms if s.get("cluster_id") == cluster_id]
|
|
161
|
+
return swarms
|
|
162
|
+
|
|
163
|
+
message = f"Fetching swarms{f' for cluster {cluster_id}' if cluster_id else ''}..."
|
|
164
|
+
swarms = self._run_with_progress(run, message)
|
|
165
|
+
|
|
166
|
+
if swarms is None:
|
|
167
|
+
return 1
|
|
168
|
+
|
|
169
|
+
# Display swarms
|
|
170
|
+
if not swarms:
|
|
171
|
+
self.print("No swarms found.")
|
|
172
|
+
return 0
|
|
173
|
+
|
|
174
|
+
table = Table(
|
|
175
|
+
title=f"User Swarms{f' (Cluster: {cluster_id})' if cluster_id else ''}"
|
|
176
|
+
)
|
|
177
|
+
table.add_column("Swarm ID", style="cyan")
|
|
178
|
+
table.add_column("Name", style="blue")
|
|
179
|
+
table.add_column("Status", style="green")
|
|
180
|
+
table.add_column("Cluster", style="magenta")
|
|
181
|
+
table.add_column("Created", style="yellow")
|
|
182
|
+
|
|
183
|
+
for swarm in swarms:
|
|
184
|
+
# Convert status enum to string
|
|
185
|
+
status = swarm.get("status")
|
|
186
|
+
status_str = (
|
|
187
|
+
enum_to_string(status, SWARM_STATUS_MAP)
|
|
188
|
+
if status is not None
|
|
189
|
+
else "Unknown"
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
# Convert timestamp
|
|
193
|
+
created_at = timestamp_to_string(
|
|
194
|
+
swarm.get("created_at") or swarm.get("creation_date")
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
table.add_row(
|
|
198
|
+
swarm.get("swarm_id", "Unknown"),
|
|
199
|
+
swarm.get("name", "Unknown"),
|
|
200
|
+
status_str,
|
|
201
|
+
swarm.get("cluster_id", "Unknown"),
|
|
202
|
+
created_at,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
self.console.print(table)
|
|
206
|
+
|
|
207
|
+
return 0
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
self.print_error(f"Failed to list swarms: {e}")
|
|
211
|
+
return 1
|
|
212
|
+
|
|
213
|
+
def show_swarm(self, swarm_id: str) -> int:
|
|
214
|
+
"""Show detailed swarm information."""
|
|
215
|
+
api = self._get_user_api()
|
|
216
|
+
if not api:
|
|
217
|
+
return 1
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
|
|
221
|
+
async def run():
|
|
222
|
+
return await api.get_swarm(swarm_id)
|
|
223
|
+
|
|
224
|
+
swarm = self._run_with_progress(run, "Fetching swarm details...")
|
|
225
|
+
|
|
226
|
+
if swarm is None:
|
|
227
|
+
return 1
|
|
228
|
+
|
|
229
|
+
# Display swarm details
|
|
230
|
+
status = swarm.get("status")
|
|
231
|
+
status_str = (
|
|
232
|
+
enum_to_string(status, SWARM_STATUS_MAP)
|
|
233
|
+
if status is not None
|
|
234
|
+
else "Unknown"
|
|
235
|
+
)
|
|
236
|
+
created_at = timestamp_to_string(
|
|
237
|
+
swarm.get("creation_date") or swarm.get("created_at")
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
panel = Panel.fit(
|
|
241
|
+
f"[cyan]Swarm ID:[/cyan] {swarm.get('swarm_id', 'Unknown')}\n"
|
|
242
|
+
f"[cyan]Name:[/cyan] {swarm.get('name', 'Unknown')}\n"
|
|
243
|
+
f"[cyan]Status:[/cyan] {status_str}\n"
|
|
244
|
+
f"[cyan]Cluster ID:[/cyan] {swarm.get('cluster_id', 'Unknown')}\n"
|
|
245
|
+
f"[cyan]Task Count:[/cyan] {swarm.get('task_count', 0)}\n"
|
|
246
|
+
f"[cyan]Node Count:[/cyan] {swarm.get('node_count', 0)}\n"
|
|
247
|
+
f"[cyan]Iteration:[/cyan] {swarm.get('iteration', 0)}\n"
|
|
248
|
+
f"[cyan]Created:[/cyan] {created_at}",
|
|
249
|
+
title=f"Swarm: {swarm_id}",
|
|
250
|
+
)
|
|
251
|
+
self.console.print(panel)
|
|
252
|
+
|
|
253
|
+
return 0
|
|
254
|
+
|
|
255
|
+
except Exception as e:
|
|
256
|
+
self.print_error(f"Failed to get swarm details: {e}")
|
|
257
|
+
return 1
|
|
258
|
+
|
|
259
|
+
def show_swarm_tasks(self, swarm_id: str) -> int:
|
|
260
|
+
"""Show tasks for a swarm."""
|
|
261
|
+
api = self._get_user_api()
|
|
262
|
+
if not api:
|
|
263
|
+
return 1
|
|
264
|
+
|
|
265
|
+
try:
|
|
266
|
+
|
|
267
|
+
async def run():
|
|
268
|
+
# Use stream_swarm_tasks and collect all tasks
|
|
269
|
+
tasks = []
|
|
270
|
+
async for task in api.stream_swarm_tasks(swarm_id):
|
|
271
|
+
tasks.append(task)
|
|
272
|
+
return tasks
|
|
273
|
+
|
|
274
|
+
tasks = self._run_with_progress(
|
|
275
|
+
run, f"Fetching tasks for swarm {swarm_id}..."
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
if tasks is None:
|
|
279
|
+
return 1
|
|
280
|
+
|
|
281
|
+
if not tasks:
|
|
282
|
+
self.print("No tasks found for this swarm.")
|
|
283
|
+
return 0
|
|
284
|
+
|
|
285
|
+
table = Table(title=f"Tasks for Swarm: {swarm_id}")
|
|
286
|
+
table.add_column("Task ID", style="cyan")
|
|
287
|
+
table.add_column("Status", style="green")
|
|
288
|
+
table.add_column("Node", style="blue")
|
|
289
|
+
table.add_column("Started", style="yellow")
|
|
290
|
+
|
|
291
|
+
for task in tasks:
|
|
292
|
+
# Convert status enum to string
|
|
293
|
+
status = task.get("status")
|
|
294
|
+
status_str = (
|
|
295
|
+
enum_to_string(status, TASK_STATUS_MAP)
|
|
296
|
+
if status is not None
|
|
297
|
+
else "Unknown"
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Convert timestamp
|
|
301
|
+
started_at = timestamp_to_string(
|
|
302
|
+
task.get("started_at") or task.get("start_time")
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
table.add_row(
|
|
306
|
+
task.get("task_id", "Unknown"),
|
|
307
|
+
status_str,
|
|
308
|
+
task.get("node_id", "Unknown"),
|
|
309
|
+
started_at,
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
self.console.print(table)
|
|
313
|
+
|
|
314
|
+
return 0
|
|
315
|
+
|
|
316
|
+
except Exception as e:
|
|
317
|
+
self.print_error(f"Failed to get swarm tasks: {e}")
|
|
318
|
+
return 1
|
|
319
|
+
|
|
320
|
+
def start_swarm(self, swarm_id: str) -> int:
|
|
321
|
+
"""Start a swarm."""
|
|
322
|
+
api = self._get_user_api()
|
|
323
|
+
if not api:
|
|
324
|
+
return 1
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
|
|
328
|
+
async def run():
|
|
329
|
+
return await api.start_swarm(swarm_id)
|
|
330
|
+
|
|
331
|
+
result = self._run_with_progress(run, f"Starting swarm {swarm_id}...")
|
|
332
|
+
|
|
333
|
+
if result is None:
|
|
334
|
+
return 1
|
|
335
|
+
|
|
336
|
+
self.print_success("Swarm started successfully")
|
|
337
|
+
return 0
|
|
338
|
+
|
|
339
|
+
except Exception as e:
|
|
340
|
+
self.print_error(f"Failed to start swarm: {e}")
|
|
341
|
+
return 1
|
|
342
|
+
|
|
343
|
+
def stop_swarm(self, swarm_id: str, force: bool = False) -> int:
|
|
344
|
+
"""Stop a swarm."""
|
|
345
|
+
if not force:
|
|
346
|
+
confirm = Confirm.ask(f"Are you sure you want to stop swarm '{swarm_id}'?")
|
|
347
|
+
if not confirm:
|
|
348
|
+
self.print("Operation cancelled.")
|
|
349
|
+
return 0
|
|
350
|
+
|
|
351
|
+
api = self._get_user_api()
|
|
352
|
+
if not api:
|
|
353
|
+
return 1
|
|
354
|
+
|
|
355
|
+
try:
|
|
356
|
+
|
|
357
|
+
async def run():
|
|
358
|
+
return await api.stop_swarm(swarm_id)
|
|
359
|
+
|
|
360
|
+
result = self._run_with_progress(run, "Stopping swarm...")
|
|
361
|
+
|
|
362
|
+
if result is None:
|
|
363
|
+
return 1
|
|
364
|
+
|
|
365
|
+
self.print_success("Swarm stopped successfully")
|
|
366
|
+
return 0
|
|
367
|
+
|
|
368
|
+
except Exception as e:
|
|
369
|
+
self.print_error(f"Failed to stop swarm: {e}")
|
|
370
|
+
return 1
|
|
371
|
+
|
|
372
|
+
def delete_swarm(self, swarm_id: str, force: bool = False) -> int:
|
|
373
|
+
"""Delete a swarm."""
|
|
374
|
+
if not force:
|
|
375
|
+
confirm = Confirm.ask(
|
|
376
|
+
f"Are you sure you want to delete swarm '{swarm_id}'?"
|
|
377
|
+
)
|
|
378
|
+
if not confirm:
|
|
379
|
+
self.print("Operation cancelled.")
|
|
380
|
+
return 0
|
|
381
|
+
|
|
382
|
+
api = self._get_user_api()
|
|
383
|
+
if not api:
|
|
384
|
+
return 1
|
|
385
|
+
|
|
386
|
+
try:
|
|
387
|
+
|
|
388
|
+
async def run():
|
|
389
|
+
return await api.remove_swarm(swarm_id)
|
|
390
|
+
|
|
391
|
+
result = self._run_with_progress(run, "Deleting swarm...")
|
|
392
|
+
|
|
393
|
+
if result is None:
|
|
394
|
+
return 1
|
|
395
|
+
|
|
396
|
+
self.print_success("Swarm deleted successfully")
|
|
397
|
+
return 0
|
|
398
|
+
|
|
399
|
+
except Exception as e:
|
|
400
|
+
self.print_error(f"Failed to delete swarm: {e}")
|
|
401
|
+
return 1
|
|
402
|
+
|
|
403
|
+
def monitor_swarm(self, swarm_id: str) -> int:
|
|
404
|
+
"""Monitor swarm execution in real-time."""
|
|
405
|
+
api = self._get_user_api()
|
|
406
|
+
if not api:
|
|
407
|
+
return 1
|
|
408
|
+
|
|
409
|
+
self.print(f"Monitoring swarm: {swarm_id}")
|
|
410
|
+
self.print("Press Ctrl+C to stop monitoring...")
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
# Set up signal handler for graceful shutdown
|
|
414
|
+
shutdown_flag = False
|
|
415
|
+
|
|
416
|
+
def signal_handler(sig, frame):
|
|
417
|
+
nonlocal shutdown_flag
|
|
418
|
+
shutdown_flag = True
|
|
419
|
+
|
|
420
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
421
|
+
|
|
422
|
+
while not shutdown_flag:
|
|
423
|
+
try:
|
|
424
|
+
|
|
425
|
+
async def get_status():
|
|
426
|
+
return await api.get_swarm(swarm_id)
|
|
427
|
+
|
|
428
|
+
swarm_status = self._run_async(get_status())
|
|
429
|
+
|
|
430
|
+
# Display current status
|
|
431
|
+
status = swarm_status.get("status")
|
|
432
|
+
status_str = (
|
|
433
|
+
enum_to_string(status, SWARM_STATUS_MAP)
|
|
434
|
+
if status is not None
|
|
435
|
+
else "Unknown"
|
|
436
|
+
)
|
|
437
|
+
status_text = f"Status: {status_str}"
|
|
438
|
+
self.console.print(f"[{time.strftime('%H:%M:%S')}] {status_text}")
|
|
439
|
+
|
|
440
|
+
# Check if swarm is finished
|
|
441
|
+
status = swarm_status.get("status", "").lower()
|
|
442
|
+
if status in ["completed", "failed", "cancelled"]:
|
|
443
|
+
self.print(f"Swarm execution {status}.")
|
|
444
|
+
return 0
|
|
445
|
+
|
|
446
|
+
time.sleep(5) # Poll every 5 seconds
|
|
447
|
+
|
|
448
|
+
except KeyboardInterrupt:
|
|
449
|
+
self.print("\nMonitoring stopped.")
|
|
450
|
+
return 130 # KeyboardInterrupt exit code
|
|
451
|
+
|
|
452
|
+
except Exception as e:
|
|
453
|
+
self.print_error(f"Monitoring failed: {e}")
|
|
454
|
+
return 1
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""SDK user and authentication commands."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from rich.panel import Panel
|
|
6
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
7
|
+
from rich.table import Table
|
|
8
|
+
|
|
9
|
+
from .base_handler import BaseSDKHandler
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SDKUserHandler(BaseSDKHandler):
|
|
13
|
+
"""Handle user-related SDK commands."""
|
|
14
|
+
|
|
15
|
+
def add_subparsers(self, parent_parser):
|
|
16
|
+
"""Add user command subparsers."""
|
|
17
|
+
subparsers = parent_parser.add_subparsers(
|
|
18
|
+
dest="user_command", help="User commands"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# User status command
|
|
22
|
+
status_parser = subparsers.add_parser(
|
|
23
|
+
"status", help="Check user service status"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Get user info command
|
|
27
|
+
get_parser = subparsers.add_parser(
|
|
28
|
+
"get-user", help="Get current user information"
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# List clusters command
|
|
32
|
+
list_clusters_parser = subparsers.add_parser(
|
|
33
|
+
"list-clusters", help="List available clusters"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# List swarms command
|
|
37
|
+
list_swarms_parser = subparsers.add_parser(
|
|
38
|
+
"list-swarms", help="List user swarms"
|
|
39
|
+
)
|
|
40
|
+
list_swarms_parser.add_argument("--cluster-id", help="Filter by cluster ID")
|
|
41
|
+
|
|
42
|
+
# Add profile override to all subcommands
|
|
43
|
+
for parser in [
|
|
44
|
+
status_parser,
|
|
45
|
+
get_parser,
|
|
46
|
+
list_clusters_parser,
|
|
47
|
+
list_swarms_parser,
|
|
48
|
+
]:
|
|
49
|
+
parser.add_argument("--profile", help="Use specific profile")
|
|
50
|
+
|
|
51
|
+
def handle(self, args) -> int:
|
|
52
|
+
"""Handle user commands."""
|
|
53
|
+
if not args.user_command:
|
|
54
|
+
self.print_error("User command required")
|
|
55
|
+
return 1
|
|
56
|
+
|
|
57
|
+
if args.user_command == "status":
|
|
58
|
+
return self.check_user_service_status()
|
|
59
|
+
elif args.user_command == "get-user":
|
|
60
|
+
return self.get_user_info()
|
|
61
|
+
elif args.user_command == "list-clusters":
|
|
62
|
+
return self.list_clusters()
|
|
63
|
+
elif args.user_command == "list-swarms":
|
|
64
|
+
return self.list_swarms(getattr(args, "cluster_id", None))
|
|
65
|
+
else:
|
|
66
|
+
self.print_error(f"Unknown user command: {args.user_command}")
|
|
67
|
+
return 1
|
|
68
|
+
|
|
69
|
+
def check_user_service_status(self) -> int:
|
|
70
|
+
"""Check user service status."""
|
|
71
|
+
api = self._get_user_api()
|
|
72
|
+
if not api:
|
|
73
|
+
return 1
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
|
|
77
|
+
async def run():
|
|
78
|
+
# Try to get user info to test connection
|
|
79
|
+
user_info = await api.get_user()
|
|
80
|
+
return user_info is not None
|
|
81
|
+
|
|
82
|
+
status = self._run_with_progress(run, "Checking user service status...")
|
|
83
|
+
|
|
84
|
+
if status:
|
|
85
|
+
self.print_success("User service is accessible")
|
|
86
|
+
return 0
|
|
87
|
+
else:
|
|
88
|
+
self.print_error("User service is not accessible")
|
|
89
|
+
return 1
|
|
90
|
+
|
|
91
|
+
except Exception as e:
|
|
92
|
+
self.print_error(f"User service check failed: {e}")
|
|
93
|
+
return 1
|
|
94
|
+
|
|
95
|
+
def get_user_info(self) -> int:
|
|
96
|
+
"""Get current user information."""
|
|
97
|
+
api = self._get_user_api()
|
|
98
|
+
if not api:
|
|
99
|
+
return 1
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
|
|
103
|
+
async def run():
|
|
104
|
+
user_info = await api.get_user()
|
|
105
|
+
return user_info
|
|
106
|
+
|
|
107
|
+
with Progress(
|
|
108
|
+
SpinnerColumn(),
|
|
109
|
+
TextColumn("[progress.description]{task.description}"),
|
|
110
|
+
console=self.console,
|
|
111
|
+
) as progress:
|
|
112
|
+
task = progress.add_task("Getting user information...", total=None)
|
|
113
|
+
user_info = asyncio.run(run())
|
|
114
|
+
progress.update(task, completed=True)
|
|
115
|
+
|
|
116
|
+
# Display user info
|
|
117
|
+
panel = Panel.fit(
|
|
118
|
+
f"[cyan]User ID:[/cyan] {user_info.get('user_id', 'Unknown')}\n"
|
|
119
|
+
f"[cyan]Username:[/cyan] {user_info.get('username', 'Unknown')}\n"
|
|
120
|
+
f"[cyan]Email:[/cyan] {user_info.get('email', 'Unknown')}\n"
|
|
121
|
+
f"[cyan]Roles:[/cyan] {', '.join(user_info.get('roles', []))}",
|
|
122
|
+
title="User Information",
|
|
123
|
+
)
|
|
124
|
+
self.console.print(panel)
|
|
125
|
+
return 0
|
|
126
|
+
|
|
127
|
+
except Exception as e:
|
|
128
|
+
self.print_error(f"Failed to get user information: {e}")
|
|
129
|
+
return 1
|
|
130
|
+
|
|
131
|
+
def list_clusters(self) -> int:
|
|
132
|
+
"""List available clusters."""
|
|
133
|
+
api = self._get_user_api()
|
|
134
|
+
if not api:
|
|
135
|
+
return 1
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
|
|
139
|
+
async def run():
|
|
140
|
+
clusters = await api.stream_and_fetch_clusters()
|
|
141
|
+
return clusters
|
|
142
|
+
|
|
143
|
+
with Progress(
|
|
144
|
+
SpinnerColumn(),
|
|
145
|
+
TextColumn("[progress.description]{task.description}"),
|
|
146
|
+
console=self.console,
|
|
147
|
+
) as progress:
|
|
148
|
+
task = progress.add_task("Fetching clusters...", total=None)
|
|
149
|
+
clusters = asyncio.run(run())
|
|
150
|
+
progress.update(task, completed=True)
|
|
151
|
+
|
|
152
|
+
# Display clusters
|
|
153
|
+
if not clusters:
|
|
154
|
+
self.print("No clusters found.")
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
table = Table(title="Available Clusters")
|
|
158
|
+
table.add_column("Cluster ID", style="cyan")
|
|
159
|
+
table.add_column("Name", style="blue")
|
|
160
|
+
table.add_column("Status", style="green")
|
|
161
|
+
table.add_column("Nodes", style="magenta")
|
|
162
|
+
|
|
163
|
+
for cluster in clusters:
|
|
164
|
+
# UserAPI always returns dictionaries
|
|
165
|
+
cluster_id = cluster.get("cluster_id", "Unknown")
|
|
166
|
+
name = cluster.get("name", "Unknown")
|
|
167
|
+
status = cluster.get("status", "Unknown")
|
|
168
|
+
node_count = str(cluster.get("node_count", 0))
|
|
169
|
+
|
|
170
|
+
table.add_row(cluster_id, name, status, node_count)
|
|
171
|
+
|
|
172
|
+
self.console.print(table)
|
|
173
|
+
|
|
174
|
+
return 0
|
|
175
|
+
|
|
176
|
+
except Exception as e:
|
|
177
|
+
self.print_error(f"Failed to list clusters: {e}")
|
|
178
|
+
return 1
|
|
179
|
+
|
|
180
|
+
def list_swarms(self, cluster_id: str = None) -> int:
|
|
181
|
+
"""List user swarms, optionally filtered by cluster."""
|
|
182
|
+
api = self._get_user_api()
|
|
183
|
+
if not api:
|
|
184
|
+
return 1
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
|
|
188
|
+
async def run():
|
|
189
|
+
# stream_and_fetch_swarms doesn't take cluster_id parameter
|
|
190
|
+
swarms = await api.stream_and_fetch_swarms()
|
|
191
|
+
# Filter by cluster_id if provided (swarms are dicts)
|
|
192
|
+
if cluster_id:
|
|
193
|
+
swarms = [s for s in swarms if s.get("cluster_id") == cluster_id]
|
|
194
|
+
return swarms
|
|
195
|
+
|
|
196
|
+
message = f"Fetching swarms{f' for cluster {cluster_id}' if cluster_id else ''}..."
|
|
197
|
+
with Progress(
|
|
198
|
+
SpinnerColumn(),
|
|
199
|
+
TextColumn("[progress.description]{task.description}"),
|
|
200
|
+
console=self.console,
|
|
201
|
+
) as progress:
|
|
202
|
+
task = progress.add_task(message, total=None)
|
|
203
|
+
swarms = asyncio.run(run())
|
|
204
|
+
progress.update(task, completed=True)
|
|
205
|
+
|
|
206
|
+
# Display swarms
|
|
207
|
+
if not swarms:
|
|
208
|
+
self.print("No swarms found.")
|
|
209
|
+
return 0
|
|
210
|
+
|
|
211
|
+
table = Table(
|
|
212
|
+
title=f"User Swarms{f' (Cluster: {cluster_id})' if cluster_id else ''}"
|
|
213
|
+
)
|
|
214
|
+
table.add_column("Swarm ID", style="cyan")
|
|
215
|
+
table.add_column("Name", style="blue")
|
|
216
|
+
table.add_column("Status", style="green")
|
|
217
|
+
table.add_column("Cluster", style="magenta")
|
|
218
|
+
table.add_column("Created", style="yellow")
|
|
219
|
+
|
|
220
|
+
for swarm in swarms:
|
|
221
|
+
table.add_row(
|
|
222
|
+
swarm.get("swarm_id", "Unknown"),
|
|
223
|
+
swarm.get("name", "Unknown"),
|
|
224
|
+
swarm.get("status", "Unknown"),
|
|
225
|
+
swarm.get("cluster_id", "Unknown"),
|
|
226
|
+
swarm.get("created_at", "Unknown"),
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
self.console.print(table)
|
|
230
|
+
return 0
|
|
231
|
+
|
|
232
|
+
except Exception as e:
|
|
233
|
+
self.print_error(f"Failed to list swarms: {e}")
|
|
234
|
+
return 1
|