vantage6 5.0.0a22__py3-none-any.whl → 5.0.0a26__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.
Potentially problematic release.
This version of vantage6 might be problematic. Click here for more details.
- tests_cli/test_client_script.py +23 -0
- vantage6/cli/__build__ +1 -1
- vantage6/cli/algorithm/generate_algorithm_json.py +529 -0
- vantage6/cli/cli.py +25 -0
- vantage6/cli/common/start.py +220 -9
- vantage6/cli/common/stop.py +90 -0
- vantage6/cli/common/utils.py +8 -7
- vantage6/cli/config.py +260 -0
- vantage6/cli/configuration_manager.py +3 -11
- vantage6/cli/configuration_wizard.py +60 -101
- vantage6/cli/context/node.py +34 -45
- vantage6/cli/context/server.py +26 -0
- vantage6/cli/dev/create.py +78 -17
- vantage6/cli/dev/data/km_dataset.csv +2401 -0
- vantage6/cli/dev/remove.py +99 -98
- vantage6/cli/globals.py +20 -0
- vantage6/cli/node/new.py +4 -3
- vantage6/cli/node/remove.py +4 -2
- vantage6/cli/node/start.py +17 -20
- vantage6/cli/prometheus/monitoring_manager.py +146 -0
- vantage6/cli/prometheus/prometheus.yml +5 -0
- vantage6/cli/server/new.py +25 -6
- vantage6/cli/server/start.py +42 -212
- vantage6/cli/server/stop.py +35 -105
- vantage6/cli/template/algo_store_config.j2 +0 -1
- vantage6/cli/template/node_config.j2 +1 -1
- vantage6/cli/template/server_import_config.j2 +0 -2
- vantage6/cli/test/algo_test_scripts/algo_test_arguments.py +29 -0
- vantage6/cli/test/algo_test_scripts/algo_test_script.py +91 -0
- vantage6/cli/test/client_script.py +151 -0
- vantage6/cli/test/common/diagnostic_runner.py +2 -2
- vantage6/cli/use/context.py +46 -0
- vantage6/cli/use/namespace.py +55 -0
- vantage6/cli/utils.py +70 -4
- {vantage6-5.0.0a22.dist-info → vantage6-5.0.0a26.dist-info}/METADATA +5 -8
- {vantage6-5.0.0a22.dist-info → vantage6-5.0.0a26.dist-info}/RECORD +39 -27
- {vantage6-5.0.0a22.dist-info → vantage6-5.0.0a26.dist-info}/WHEEL +0 -0
- {vantage6-5.0.0a22.dist-info → vantage6-5.0.0a26.dist-info}/entry_points.txt +0 -0
- {vantage6-5.0.0a22.dist-info → vantage6-5.0.0a26.dist-info}/top_level.txt +0 -0
vantage6/cli/common/start.py
CHANGED
|
@@ -1,29 +1,41 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import enum
|
|
1
4
|
import os
|
|
2
|
-
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
3
7
|
import time
|
|
8
|
+
from os import PathLike
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from threading import Thread
|
|
11
|
+
|
|
4
12
|
import docker
|
|
5
|
-
import
|
|
13
|
+
from colorama import Fore, Style
|
|
6
14
|
from docker.client import DockerClient
|
|
7
15
|
from docker.models.containers import Container
|
|
8
|
-
from colorama import Fore, Style
|
|
9
16
|
from sqlalchemy.engine.url import make_url
|
|
10
17
|
|
|
11
18
|
from vantage6.common import error, info, warning
|
|
12
19
|
from vantage6.common.context import AppContext
|
|
20
|
+
from vantage6.common.docker.addons import check_docker_running, pull_image
|
|
13
21
|
from vantage6.common.globals import (
|
|
22
|
+
APPNAME,
|
|
14
23
|
DEFAULT_ALGO_STORE_IMAGE,
|
|
24
|
+
DEFAULT_CHART_REPO,
|
|
25
|
+
DEFAULT_DOCKER_REGISTRY,
|
|
15
26
|
DEFAULT_NODE_IMAGE,
|
|
16
27
|
DEFAULT_SERVER_IMAGE,
|
|
17
28
|
DEFAULT_UI_IMAGE,
|
|
18
29
|
InstanceType,
|
|
19
|
-
APPNAME,
|
|
20
|
-
DEFAULT_DOCKER_REGISTRY,
|
|
21
30
|
)
|
|
22
|
-
|
|
23
|
-
from vantage6.cli.context import AlgorithmStoreContext, ServerContext
|
|
31
|
+
|
|
24
32
|
from vantage6.cli.common.utils import print_log_worker
|
|
25
|
-
from vantage6.cli.
|
|
26
|
-
from vantage6.cli.globals import
|
|
33
|
+
from vantage6.cli.context import AlgorithmStoreContext, ServerContext
|
|
34
|
+
from vantage6.cli.globals import AlgoStoreGlobals, ServerGlobals
|
|
35
|
+
from vantage6.cli.utils import (
|
|
36
|
+
validate_input_cmd_args,
|
|
37
|
+
check_config_name_allowed,
|
|
38
|
+
)
|
|
27
39
|
|
|
28
40
|
|
|
29
41
|
def check_for_start(ctx: AppContext, type_: InstanceType) -> DockerClient:
|
|
@@ -279,6 +291,8 @@ def mount_database(
|
|
|
279
291
|
return mount, environment_vars
|
|
280
292
|
|
|
281
293
|
|
|
294
|
+
# TODO v5+ remove this function, it is replaced by the `attach_logs` function in
|
|
295
|
+
# `vantage6.cli.common.utils`
|
|
282
296
|
def attach_logs(container: Container, type_: InstanceType) -> None:
|
|
283
297
|
"""
|
|
284
298
|
Attach container logs to the console if specified.
|
|
@@ -304,3 +318,200 @@ def attach_logs(container: Container, type_: InstanceType) -> None:
|
|
|
304
318
|
f"with {Fore.RED}v6 {type_} stop{Style.RESET_ALL}"
|
|
305
319
|
)
|
|
306
320
|
exit(0)
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def helm_install(
|
|
324
|
+
release_name: str,
|
|
325
|
+
chart_name: str,
|
|
326
|
+
values_file: str | PathLike | None = None,
|
|
327
|
+
context: str | None = None,
|
|
328
|
+
namespace: str | None = None,
|
|
329
|
+
) -> None:
|
|
330
|
+
"""
|
|
331
|
+
Manage the `helm install` command.
|
|
332
|
+
|
|
333
|
+
Parameters
|
|
334
|
+
----------
|
|
335
|
+
release_name : str
|
|
336
|
+
The name of the Helm release.
|
|
337
|
+
chart_name : str
|
|
338
|
+
The name of the Helm chart.
|
|
339
|
+
values_file : str, optional
|
|
340
|
+
A single values file to use with the `-f` flag.
|
|
341
|
+
context : str, optional
|
|
342
|
+
The Kubernetes context to use.
|
|
343
|
+
namespace : str, optional
|
|
344
|
+
The Kubernetes namespace to use.
|
|
345
|
+
"""
|
|
346
|
+
# Input validation
|
|
347
|
+
validate_input_cmd_args(release_name, "release name")
|
|
348
|
+
validate_input_cmd_args(chart_name, "chart name")
|
|
349
|
+
|
|
350
|
+
values_file = Path(values_file) if values_file else None
|
|
351
|
+
if values_file and not values_file.is_file():
|
|
352
|
+
error(f"Helm chart values file does not exist: {values_file}")
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
validate_input_cmd_args(context, "context name", allow_none=True)
|
|
356
|
+
validate_input_cmd_args(namespace, "namespace name", allow_none=True)
|
|
357
|
+
|
|
358
|
+
# Create the command
|
|
359
|
+
command = [
|
|
360
|
+
"helm",
|
|
361
|
+
"install",
|
|
362
|
+
release_name,
|
|
363
|
+
chart_name,
|
|
364
|
+
"--repo",
|
|
365
|
+
DEFAULT_CHART_REPO,
|
|
366
|
+
"--devel", # ensure using latest version including pre-releases
|
|
367
|
+
]
|
|
368
|
+
|
|
369
|
+
if values_file:
|
|
370
|
+
command.extend(["-f", str(values_file)])
|
|
371
|
+
|
|
372
|
+
if context:
|
|
373
|
+
command.extend(["--kube-context", context])
|
|
374
|
+
|
|
375
|
+
if namespace:
|
|
376
|
+
command.extend(["--namespace", namespace])
|
|
377
|
+
|
|
378
|
+
try:
|
|
379
|
+
subprocess.run(
|
|
380
|
+
command,
|
|
381
|
+
stdout=subprocess.DEVNULL,
|
|
382
|
+
check=True,
|
|
383
|
+
)
|
|
384
|
+
info(
|
|
385
|
+
f"Successfully installed release '{release_name}' using chart '{chart_name}'."
|
|
386
|
+
)
|
|
387
|
+
except subprocess.CalledProcessError as e:
|
|
388
|
+
error(f"Failed to install release '{release_name}': {e.stderr}")
|
|
389
|
+
except FileNotFoundError:
|
|
390
|
+
error(
|
|
391
|
+
"Helm command not found. Please ensure Helm is installed and available in the PATH."
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def start_port_forward(
|
|
396
|
+
service_name: str,
|
|
397
|
+
service_port: int,
|
|
398
|
+
port: int,
|
|
399
|
+
ip: str | None,
|
|
400
|
+
context: str | None = None,
|
|
401
|
+
namespace: str | None = None,
|
|
402
|
+
) -> None:
|
|
403
|
+
"""
|
|
404
|
+
Port forward a kubernetes service.
|
|
405
|
+
|
|
406
|
+
Parameters
|
|
407
|
+
----------
|
|
408
|
+
service_name : str
|
|
409
|
+
The name of the Kubernetes service to port forward.
|
|
410
|
+
service_port : int
|
|
411
|
+
The port on the service to forward.
|
|
412
|
+
port : int
|
|
413
|
+
The port to listen on.
|
|
414
|
+
ip : str | None
|
|
415
|
+
The IP address to listen on. If None, defaults to localhost.
|
|
416
|
+
context : str | None
|
|
417
|
+
The Kubernetes context to use.
|
|
418
|
+
namespace : str | None
|
|
419
|
+
The Kubernetes namespace to use.
|
|
420
|
+
"""
|
|
421
|
+
# Input validation
|
|
422
|
+
validate_input_cmd_args(service_name, "service name")
|
|
423
|
+
if not isinstance(service_port, int) or service_port <= 0:
|
|
424
|
+
error(f"Invalid service port: {service_port}. Must be a positive integer.")
|
|
425
|
+
return
|
|
426
|
+
|
|
427
|
+
if not isinstance(port, int) or port <= 0:
|
|
428
|
+
error(f"Invalid local port: {port}. Must be a positive integer.")
|
|
429
|
+
return
|
|
430
|
+
|
|
431
|
+
if ip and not re.match(
|
|
432
|
+
r"^(localhost|[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$", ip
|
|
433
|
+
):
|
|
434
|
+
error(f"Invalid IP address: {ip}. Must be a valid IPv4 address or 'localhost'.")
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
validate_input_cmd_args(context, "context name", allow_none=True)
|
|
438
|
+
validate_input_cmd_args(namespace, "namespace name", allow_none=True)
|
|
439
|
+
|
|
440
|
+
# Check if the service is ready before starting port forwarding
|
|
441
|
+
info(f"Waiting for service '{service_name}' to become ready...")
|
|
442
|
+
start_time = time.time()
|
|
443
|
+
timeout = 300 # seconds
|
|
444
|
+
while time.time() - start_time < timeout:
|
|
445
|
+
try:
|
|
446
|
+
result = (
|
|
447
|
+
subprocess.check_output(
|
|
448
|
+
[
|
|
449
|
+
"kubectl",
|
|
450
|
+
"get",
|
|
451
|
+
"endpoints",
|
|
452
|
+
service_name,
|
|
453
|
+
"-o",
|
|
454
|
+
"jsonpath={.subsets[*].addresses[*].ip}",
|
|
455
|
+
]
|
|
456
|
+
)
|
|
457
|
+
.decode()
|
|
458
|
+
.strip()
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
if result:
|
|
462
|
+
info(f"Service '{service_name}' is ready.")
|
|
463
|
+
break
|
|
464
|
+
except subprocess.CalledProcessError:
|
|
465
|
+
pass # ignore and retry
|
|
466
|
+
|
|
467
|
+
time.sleep(2)
|
|
468
|
+
else:
|
|
469
|
+
error(
|
|
470
|
+
f"Timeout: Service '{service_name}' has no ready endpoints after {timeout} seconds."
|
|
471
|
+
)
|
|
472
|
+
return
|
|
473
|
+
|
|
474
|
+
# Create the port forwarding command
|
|
475
|
+
if not ip:
|
|
476
|
+
ip = "localhost"
|
|
477
|
+
|
|
478
|
+
command = [
|
|
479
|
+
"kubectl",
|
|
480
|
+
"port-forward",
|
|
481
|
+
"--address",
|
|
482
|
+
ip,
|
|
483
|
+
f"service/{service_name}",
|
|
484
|
+
f"{port}:{service_port}",
|
|
485
|
+
]
|
|
486
|
+
|
|
487
|
+
if context:
|
|
488
|
+
command.extend(["--context", context])
|
|
489
|
+
|
|
490
|
+
if namespace:
|
|
491
|
+
command.extend(["--namespace", namespace])
|
|
492
|
+
|
|
493
|
+
# Start the port forwarding process
|
|
494
|
+
try:
|
|
495
|
+
process = subprocess.Popen(
|
|
496
|
+
command,
|
|
497
|
+
stdout=subprocess.DEVNULL,
|
|
498
|
+
stderr=subprocess.PIPE,
|
|
499
|
+
start_new_session=True, # Start in new session to detach from parent
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
# Give the process a moment to start and check if it's still running
|
|
503
|
+
time.sleep(1)
|
|
504
|
+
if process.poll() is not None:
|
|
505
|
+
# Process has already terminated
|
|
506
|
+
e = process.stderr.read().decode() if process.stderr else "Unknown error"
|
|
507
|
+
error(f"Failed to start port forwarding: {e}")
|
|
508
|
+
return
|
|
509
|
+
|
|
510
|
+
info(
|
|
511
|
+
f"Port forwarding started: {ip}:{port} -> {service_name}:{service_port} "
|
|
512
|
+
f"(PID: {str(process.pid)})"
|
|
513
|
+
)
|
|
514
|
+
return
|
|
515
|
+
except Exception as e:
|
|
516
|
+
error(f"Failed to start port forwarding: {e}")
|
|
517
|
+
return
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
from vantage6.common import error, info, warning
|
|
7
|
+
from vantage6.cli.utils import validate_input_cmd_args
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def stop_port_forward(service_name: str) -> None:
|
|
11
|
+
"""
|
|
12
|
+
Stop the port forwarding process for a given service name.
|
|
13
|
+
|
|
14
|
+
Parameters
|
|
15
|
+
----------
|
|
16
|
+
service_name : str
|
|
17
|
+
The name of the service whose port forwarding process should be terminated.
|
|
18
|
+
"""
|
|
19
|
+
# Input validation
|
|
20
|
+
validate_input_cmd_args(service_name, "service name")
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
# Find the process ID (PID) of the port forwarding command
|
|
24
|
+
result = subprocess.run(
|
|
25
|
+
["pgrep", "-f", f"kubectl port-forward.*{service_name}"],
|
|
26
|
+
check=True,
|
|
27
|
+
text=True,
|
|
28
|
+
capture_output=True,
|
|
29
|
+
)
|
|
30
|
+
pids = result.stdout.strip().splitlines()
|
|
31
|
+
|
|
32
|
+
if not pids:
|
|
33
|
+
warning(f"No port forwarding process found for service '{service_name}'.")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
for pid in pids:
|
|
37
|
+
subprocess.run(["kill", "-9", pid], check=True)
|
|
38
|
+
info(
|
|
39
|
+
f"Terminated port forwarding process for service '{service_name}' "
|
|
40
|
+
f"(PID: {pid})"
|
|
41
|
+
)
|
|
42
|
+
except subprocess.CalledProcessError as e:
|
|
43
|
+
error(f"Failed to terminate port forwarding: {e}")
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def helm_uninstall(
|
|
47
|
+
release_name: str,
|
|
48
|
+
context: str | None = None,
|
|
49
|
+
namespace: str | None = None,
|
|
50
|
+
) -> None:
|
|
51
|
+
"""
|
|
52
|
+
Manage the `helm uninstall` command.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
release_name : str
|
|
57
|
+
The name of the Helm release to uninstall.
|
|
58
|
+
context : str, optional
|
|
59
|
+
The Kubernetes context to use.
|
|
60
|
+
namespace : str, optional
|
|
61
|
+
The Kubernetes namespace to use.
|
|
62
|
+
"""
|
|
63
|
+
# Input validation
|
|
64
|
+
validate_input_cmd_args(release_name, "release name")
|
|
65
|
+
validate_input_cmd_args(context, "context name", allow_none=True)
|
|
66
|
+
validate_input_cmd_args(namespace, "namespace name", allow_none=True)
|
|
67
|
+
|
|
68
|
+
# Create the command
|
|
69
|
+
command = ["helm", "uninstall", release_name]
|
|
70
|
+
|
|
71
|
+
if context:
|
|
72
|
+
command.extend(["--kube-context", context])
|
|
73
|
+
|
|
74
|
+
if namespace:
|
|
75
|
+
command.extend(["--namespace", namespace])
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
subprocess.run(
|
|
79
|
+
command,
|
|
80
|
+
stdout=subprocess.DEVNULL,
|
|
81
|
+
check=True,
|
|
82
|
+
)
|
|
83
|
+
info(f"Successfully uninstalled release '{release_name}'.")
|
|
84
|
+
except subprocess.CalledProcessError as e:
|
|
85
|
+
error(f"Failed to uninstall release '{release_name}': {e.stderr}")
|
|
86
|
+
except FileNotFoundError:
|
|
87
|
+
error(
|
|
88
|
+
"Helm command not found. Please ensure Helm is installed and available in "
|
|
89
|
+
"the PATH."
|
|
90
|
+
)
|
vantage6/cli/common/utils.py
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import enum
|
|
2
|
-
|
|
3
|
-
from colorama import Fore, Style
|
|
4
|
-
import click
|
|
2
|
+
from subprocess import Popen
|
|
5
3
|
from typing import Iterable
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
6
|
import docker
|
|
7
|
-
|
|
7
|
+
import questionary as q
|
|
8
|
+
from colorama import Fore, Style
|
|
8
9
|
|
|
10
|
+
from vantage6.common import error, warning
|
|
11
|
+
from vantage6.common.globals import APPNAME, STRING_ENCODING, InstanceType
|
|
9
12
|
|
|
10
|
-
from vantage6.common import warning, error
|
|
11
|
-
from vantage6.common.globals import APPNAME, InstanceType, STRING_ENCODING
|
|
12
13
|
from vantage6.cli.context import select_context_class
|
|
13
14
|
|
|
14
15
|
|
|
@@ -177,6 +178,6 @@ def attach_logs(*labels: list[str]) -> None:
|
|
|
177
178
|
labels : list[str]
|
|
178
179
|
The labels to attach to
|
|
179
180
|
"""
|
|
180
|
-
command = ["
|
|
181
|
+
command = ["kubectl", "logs", "--follow", "--selector", ",".join(labels)]
|
|
181
182
|
process = Popen(command, stdout=None, stderr=None)
|
|
182
183
|
process.wait()
|
vantage6/cli/config.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from os import PathLike
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import questionary
|
|
6
|
+
import yaml
|
|
7
|
+
from colorama import Fore, Style
|
|
8
|
+
from kubernetes import config
|
|
9
|
+
|
|
10
|
+
from vantage6.common import info, warning
|
|
11
|
+
|
|
12
|
+
from vantage6.cli.globals import (
|
|
13
|
+
DEFAULT_CLI_CONFIG_FILE,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class CliConfig:
|
|
18
|
+
"""
|
|
19
|
+
A class to manage CLI configuration for Kubernetes context and namespace.
|
|
20
|
+
|
|
21
|
+
The CLI configuration is stored in a `config.yaml` file located at the path
|
|
22
|
+
specified by `DEFAULT_CLI_CONFIG_FILE`.
|
|
23
|
+
|
|
24
|
+
The `config.yaml` file has the following structure:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
kube:
|
|
28
|
+
last_context: <last_used_k8s_context>
|
|
29
|
+
last_namespace: <last_used_k8s_namespace>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Attributes
|
|
33
|
+
----------
|
|
34
|
+
config_path : PathLike
|
|
35
|
+
Path to the configuration file.
|
|
36
|
+
_cached_config : dict or None
|
|
37
|
+
Cached configuration data.
|
|
38
|
+
_cached_mtime : float or None
|
|
39
|
+
Last modification time of the configuration file.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, config_path: str | PathLike = DEFAULT_CLI_CONFIG_FILE) -> None:
|
|
43
|
+
"""
|
|
44
|
+
Initialize the CliConfig object.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
config_path : str or PathLike, optional
|
|
49
|
+
Path to the configuration file.
|
|
50
|
+
"""
|
|
51
|
+
self.config_path: PathLike = Path(config_path)
|
|
52
|
+
self._cached_config: dict | None = None
|
|
53
|
+
self._cached_mtime: float | None = None
|
|
54
|
+
|
|
55
|
+
def _load_config(self) -> dict:
|
|
56
|
+
"""
|
|
57
|
+
Load the configuration from the file.
|
|
58
|
+
|
|
59
|
+
Returns
|
|
60
|
+
-------
|
|
61
|
+
dict
|
|
62
|
+
The loaded configuration data.
|
|
63
|
+
"""
|
|
64
|
+
if self.config_path.exists():
|
|
65
|
+
with open(self.config_path, "r") as config_file:
|
|
66
|
+
return yaml.safe_load(config_file) or {}
|
|
67
|
+
return {}
|
|
68
|
+
|
|
69
|
+
def _save_config(self, config: dict) -> None:
|
|
70
|
+
"""
|
|
71
|
+
Save the configuration to the file.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
config : dict
|
|
76
|
+
The configuration data to save.
|
|
77
|
+
"""
|
|
78
|
+
self.config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
79
|
+
with open(self.config_path, "w") as config_file:
|
|
80
|
+
yaml.dump(config, config_file)
|
|
81
|
+
|
|
82
|
+
def _reload_cache_lazy(self) -> None:
|
|
83
|
+
"""
|
|
84
|
+
Reload the configuration cache if the file has been modified.
|
|
85
|
+
"""
|
|
86
|
+
if self.config_path.exists():
|
|
87
|
+
mtime = os.path.getmtime(self.config_path)
|
|
88
|
+
if self._cached_mtime != mtime:
|
|
89
|
+
self._cached_config = self._load_config()
|
|
90
|
+
self._cached_mtime = mtime
|
|
91
|
+
else:
|
|
92
|
+
self._cached_config = {}
|
|
93
|
+
self._cached_mtime = None
|
|
94
|
+
|
|
95
|
+
def get_last_context(self) -> str | None:
|
|
96
|
+
"""
|
|
97
|
+
Get the last used Kubernetes context.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
str or None
|
|
102
|
+
The last used Kubernetes context, or None if not set.
|
|
103
|
+
"""
|
|
104
|
+
self._reload_cache_lazy()
|
|
105
|
+
return self._cached_config.get("kube", {}).get("last_context")
|
|
106
|
+
|
|
107
|
+
def set_last_context(self, context: str) -> None:
|
|
108
|
+
"""
|
|
109
|
+
Set the Kubernetes context.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
context : str
|
|
114
|
+
The Kubernetes context to set.
|
|
115
|
+
"""
|
|
116
|
+
config = self._load_config()
|
|
117
|
+
if "kube" not in config:
|
|
118
|
+
config["kube"] = {}
|
|
119
|
+
|
|
120
|
+
if config["kube"].get("last_context") != context:
|
|
121
|
+
config["kube"]["last_context"] = context
|
|
122
|
+
self._save_config(config)
|
|
123
|
+
self._reload_cache_lazy()
|
|
124
|
+
|
|
125
|
+
def get_last_namespace(self) -> str | None:
|
|
126
|
+
"""
|
|
127
|
+
Get the last used Kubernetes namespace.
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
str or None
|
|
132
|
+
The last used Kubernetes namespace, or None if not set.
|
|
133
|
+
"""
|
|
134
|
+
self._reload_cache_lazy()
|
|
135
|
+
return self._cached_config.get("kube", {}).get("last_namespace")
|
|
136
|
+
|
|
137
|
+
def set_last_namespace(self, namespace: str) -> None:
|
|
138
|
+
"""
|
|
139
|
+
Set the Kubernetes namespace.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
namespace : str
|
|
144
|
+
The Kubernetes namespace to set.
|
|
145
|
+
"""
|
|
146
|
+
config = self._load_config()
|
|
147
|
+
if "kube" not in config:
|
|
148
|
+
config["kube"] = {}
|
|
149
|
+
|
|
150
|
+
if config["kube"].get("last_namespace") != namespace:
|
|
151
|
+
config["kube"]["last_namespace"] = namespace
|
|
152
|
+
self._save_config(config)
|
|
153
|
+
self._reload_cache_lazy()
|
|
154
|
+
|
|
155
|
+
def remove_kube(self) -> None:
|
|
156
|
+
"""
|
|
157
|
+
Remove the last used context and namespace.
|
|
158
|
+
"""
|
|
159
|
+
if self.config_path.exists():
|
|
160
|
+
config = self._load_config()
|
|
161
|
+
if "kube" in config:
|
|
162
|
+
del config["kube"]
|
|
163
|
+
self._save_config(config)
|
|
164
|
+
self._reload_cache_lazy()
|
|
165
|
+
|
|
166
|
+
def get_active_settings(
|
|
167
|
+
self,
|
|
168
|
+
context: str | None,
|
|
169
|
+
namespace: str | None,
|
|
170
|
+
) -> tuple[str, str]:
|
|
171
|
+
"""
|
|
172
|
+
Get the active Kubernetes context and namespace.
|
|
173
|
+
|
|
174
|
+
Parameters
|
|
175
|
+
----------
|
|
176
|
+
context : str or None
|
|
177
|
+
The Kubernetes context to use.
|
|
178
|
+
namespace : str or None
|
|
179
|
+
The Kubernetes namespace to use.
|
|
180
|
+
|
|
181
|
+
Returns
|
|
182
|
+
-------
|
|
183
|
+
tuple[str, str]
|
|
184
|
+
A tuple containing the active context and namespace.
|
|
185
|
+
"""
|
|
186
|
+
if not context:
|
|
187
|
+
_, active_context = config.list_kube_config_contexts()
|
|
188
|
+
context = active_context["name"]
|
|
189
|
+
|
|
190
|
+
if not namespace:
|
|
191
|
+
namespace = active_context["context"].get("namespace", "default")
|
|
192
|
+
|
|
193
|
+
return context, namespace
|
|
194
|
+
|
|
195
|
+
def compare_changes_config(
|
|
196
|
+
self,
|
|
197
|
+
context: str | None = None,
|
|
198
|
+
namespace: str | None = None,
|
|
199
|
+
) -> tuple[str, str]:
|
|
200
|
+
"""
|
|
201
|
+
Compare active settings with last used settings.
|
|
202
|
+
|
|
203
|
+
Parameters
|
|
204
|
+
----------
|
|
205
|
+
context : str or None, optional
|
|
206
|
+
The Kubernetes context to use.
|
|
207
|
+
namespace : str or None, optional
|
|
208
|
+
The Kubernetes namespace to use.
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
tuple[str, str]
|
|
213
|
+
A tuple containing the active context and namespace.
|
|
214
|
+
"""
|
|
215
|
+
|
|
216
|
+
active_context, active_namespace = self.get_active_settings(context, namespace)
|
|
217
|
+
last_context = self.get_last_context()
|
|
218
|
+
last_namespace = self.get_last_namespace()
|
|
219
|
+
|
|
220
|
+
# compare context
|
|
221
|
+
if not last_context:
|
|
222
|
+
self.set_last_context(context=active_context)
|
|
223
|
+
elif last_context != active_context:
|
|
224
|
+
warning("Are you using the correct context?")
|
|
225
|
+
warning(f"Current context: {Fore.YELLOW}{active_context}{Style.RESET_ALL}")
|
|
226
|
+
warning(f"Last context: {Fore.YELLOW}{last_context}{Style.RESET_ALL}")
|
|
227
|
+
|
|
228
|
+
active_context = questionary.select(
|
|
229
|
+
"Which context do you want to use?",
|
|
230
|
+
choices=[active_context, last_context],
|
|
231
|
+
default=active_context,
|
|
232
|
+
).ask()
|
|
233
|
+
|
|
234
|
+
if last_context != active_context:
|
|
235
|
+
self.set_last_context(context=active_context)
|
|
236
|
+
|
|
237
|
+
# compare namespace
|
|
238
|
+
if not last_namespace:
|
|
239
|
+
self.set_last_namespace(namespace=active_namespace)
|
|
240
|
+
elif last_namespace != active_namespace:
|
|
241
|
+
warning("Are you using the correct namespace?")
|
|
242
|
+
warning(
|
|
243
|
+
f"Current namespace: {Fore.YELLOW}{active_namespace}{Style.RESET_ALL}"
|
|
244
|
+
)
|
|
245
|
+
warning(
|
|
246
|
+
f"Last namespace: {Fore.YELLOW}{last_namespace}{Style.RESET_ALL}"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
active_namespace = questionary.select(
|
|
250
|
+
"Which namespace do you want to use?",
|
|
251
|
+
choices=[active_namespace, last_namespace],
|
|
252
|
+
default=active_namespace,
|
|
253
|
+
).ask()
|
|
254
|
+
|
|
255
|
+
if last_namespace != active_namespace:
|
|
256
|
+
self.set_last_namespace(namespace=active_namespace)
|
|
257
|
+
|
|
258
|
+
info(f"Using context: {Fore.YELLOW}{active_context}{Style.RESET_ALL}")
|
|
259
|
+
info(f"Using namespace: {Fore.YELLOW}{active_namespace}{Style.RESET_ALL}")
|
|
260
|
+
return active_context, active_namespace
|
|
@@ -20,17 +20,8 @@ class ServerConfiguration(Configuration):
|
|
|
20
20
|
validators.
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"ip": Use(str),
|
|
26
|
-
"port": Use(int),
|
|
27
|
-
Optional("api_path"): str,
|
|
28
|
-
"uri": Use(str),
|
|
29
|
-
"allow_drop_all": Use(bool),
|
|
30
|
-
"logging": {**LOGGING_VALIDATORS, "file": Use(str)},
|
|
31
|
-
Optional("server_name"): str,
|
|
32
|
-
Optional("runs_data_cleanup_days"): Use(int),
|
|
33
|
-
}
|
|
23
|
+
# TODO: explore how to validate helm values.yaml files, see issue 2105
|
|
24
|
+
VALIDATORS = {}
|
|
34
25
|
|
|
35
26
|
|
|
36
27
|
class NodeConfiguration(Configuration):
|
|
@@ -50,6 +41,7 @@ class NodeConfiguration(Configuration):
|
|
|
50
41
|
Optional("node_extra_env"): dict,
|
|
51
42
|
Optional("node_extra_mounts"): [str],
|
|
52
43
|
Optional("node_extra_hosts"): dict,
|
|
44
|
+
Optional("share_algorithm_logs"): Use(bool),
|
|
53
45
|
}
|
|
54
46
|
|
|
55
47
|
|