vantage6 5.0.0a37__py3-none-any.whl → 5.0.0a38__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.

Files changed (43) hide show
  1. vantage6/cli/algostore/attach.py +28 -3
  2. vantage6/cli/algostore/list.py +2 -2
  3. vantage6/cli/algostore/start.py +11 -3
  4. vantage6/cli/algostore/version.py +62 -0
  5. vantage6/cli/auth/attach.py +1 -1
  6. vantage6/cli/auth/list.py +2 -2
  7. vantage6/cli/auth/remove.py +58 -0
  8. vantage6/cli/auth/start.py +12 -8
  9. vantage6/cli/cli.py +2 -0
  10. vantage6/cli/common/attach.py +114 -0
  11. vantage6/cli/common/decorator.py +5 -3
  12. vantage6/cli/common/list.py +68 -0
  13. vantage6/cli/common/remove.py +18 -0
  14. vantage6/cli/common/stop.py +3 -9
  15. vantage6/cli/common/utils.py +44 -76
  16. vantage6/cli/common/version.py +82 -0
  17. vantage6/cli/context/__init__.py +3 -0
  18. vantage6/cli/context/algorithm_store.py +2 -2
  19. vantage6/cli/node/attach.py +27 -3
  20. vantage6/cli/node/list.py +3 -44
  21. vantage6/cli/node/start.py +11 -3
  22. vantage6/cli/node/stop.py +13 -15
  23. vantage6/cli/node/version.py +96 -33
  24. vantage6/cli/sandbox/config/base.py +10 -2
  25. vantage6/cli/sandbox/config/core.py +3 -3
  26. vantage6/cli/sandbox/config/node.py +2 -5
  27. vantage6/cli/sandbox/remove.py +17 -35
  28. vantage6/cli/sandbox/start.py +8 -0
  29. vantage6/cli/sandbox/stop.py +1 -1
  30. vantage6/cli/server/attach.py +28 -3
  31. vantage6/cli/server/list.py +2 -2
  32. vantage6/cli/server/start.py +11 -3
  33. vantage6/cli/server/version.py +31 -18
  34. vantage6/cli/template/algo_store_config.j2 +3 -0
  35. vantage6/cli/template/node_config.j2 +2 -0
  36. vantage6/cli/use/context.py +8 -1
  37. vantage6/cli/use/namespace.py +10 -7
  38. vantage6/cli/utils_kubernetes.py +270 -0
  39. {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a38.dist-info}/METADATA +3 -3
  40. {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a38.dist-info}/RECORD +43 -38
  41. /vantage6/cli/node/{task_cleanup/__init__.py → common/task_cleanup.py} +0 -0
  42. {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a38.dist-info}/WHEEL +0 -0
  43. {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a38.dist-info}/entry_points.txt +0 -0
@@ -1,19 +1,20 @@
1
1
  import json
2
2
  import subprocess
3
3
  from pathlib import Path
4
- from subprocess import Popen
5
4
  from typing import Iterable
6
5
 
7
- import click
8
6
  import docker
9
7
  import questionary as q
10
- from colorama import Fore, Style
11
8
 
12
- from vantage6.common import error, warning
13
- from vantage6.common.globals import APPNAME, STRING_ENCODING, InstanceType
9
+ from vantage6.common import error
10
+ from vantage6.common.globals import (
11
+ APPNAME,
12
+ SANDBOX_SUFFIX,
13
+ STRING_ENCODING,
14
+ InstanceType,
15
+ )
14
16
 
15
17
  from vantage6.cli.config import CliConfig
16
- from vantage6.cli.context import select_context_class
17
18
  from vantage6.cli.globals import CLICommandName
18
19
  from vantage6.cli.utils import validate_input_cmd_args
19
20
 
@@ -251,60 +252,6 @@ def get_running_servers(
251
252
  return [server.name for server in running_servers]
252
253
 
253
254
 
254
- def get_server_configuration_list(instance_type: InstanceType) -> None:
255
- """
256
- Print list of available server configurations.
257
-
258
- Parameters
259
- ----------
260
- instance_type : InstanceType
261
- The type of instance to get the configurations for
262
- """
263
- ctx_class = select_context_class(instance_type)
264
-
265
- running_server_names = find_running_service_names(instance_type)
266
- header = "\nName" + (21 * " ") + "Status" + (10 * " ") + "System/User"
267
-
268
- click.echo(header)
269
- click.echo("-" * len(header))
270
-
271
- running = Fore.GREEN + "Running" + Style.RESET_ALL
272
- stopped = Fore.RED + "Not running" + Style.RESET_ALL
273
-
274
- # system folders
275
- configs, failed_imports_system = ctx_class.available_configurations(
276
- system_folders=True
277
- )
278
- for config in configs:
279
- status = (
280
- running
281
- if f"{APPNAME}-{config.name}-system-{instance_type.value}"
282
- in running_server_names
283
- else stopped
284
- )
285
- click.echo(f"{config.name:25}{status:25} System ")
286
-
287
- # user folders
288
- configs, failed_imports_user = ctx_class.available_configurations(
289
- system_folders=False
290
- )
291
- for config in configs:
292
- status = (
293
- running
294
- if f"{APPNAME}-{config.name}-user-{instance_type.value}"
295
- in running_server_names
296
- else stopped
297
- )
298
- click.echo(f"{config.name:25}{status:25} User ")
299
-
300
- click.echo("-" * 85)
301
- if len(failed_imports_system) + len(failed_imports_user):
302
- warning(
303
- f"{Fore.RED}Failed imports: "
304
- f"{len(failed_imports_system) + len(failed_imports_user)}{Style.RESET_ALL}"
305
- )
306
-
307
-
308
255
  def print_log_worker(logs_stream: Iterable[bytes]) -> None:
309
256
  """
310
257
  Print the logs from the logs stream.
@@ -354,20 +301,6 @@ def get_config_name_from_service_name(service_name: str) -> str:
354
301
  return "-".join(service_name.split("-")[1:-2])
355
302
 
356
303
 
357
- def attach_logs(*labels: list[str]) -> None:
358
- """
359
- Attach to the logs of the given labels.
360
-
361
- Parameters
362
- ----------
363
- labels : list[str]
364
- The labels to attach to
365
- """
366
- command = ["kubectl", "logs", "--follow", "--selector", ",".join(labels)]
367
- process = Popen(command, stdout=None, stderr=None)
368
- process.wait()
369
-
370
-
371
304
  def get_main_cli_command_name(instance_type: InstanceType) -> str:
372
305
  """
373
306
  Get the main CLI command name for a given instance type.
@@ -419,7 +352,9 @@ def check_running(
419
352
  return helm_release_name in running_services
420
353
 
421
354
 
422
- def get_config_name_from_helm_release_name(helm_release_name: str) -> str:
355
+ def get_config_name_from_helm_release_name(
356
+ helm_release_name: str, is_store: bool = False
357
+ ) -> str:
423
358
  """
424
359
  Get the config name from a helm release name.
425
360
 
@@ -427,6 +362,8 @@ def get_config_name_from_helm_release_name(helm_release_name: str) -> str:
427
362
  ----------
428
363
  helm_release_name : str
429
364
  The name of the Helm release
365
+ is_store : bool, optional
366
+ Whether the instance is a store or not. By default False.
430
367
 
431
368
  Returns
432
369
  -------
@@ -436,4 +373,35 @@ def get_config_name_from_helm_release_name(helm_release_name: str) -> str:
436
373
  # helm release name is structured as:
437
374
  # f"{APPNAME}-{name}-{scope}-{instance_type}"
438
375
  # we want to get the name from the service name
439
- return "-".join(helm_release_name.split("-")[1:-2])
376
+ if is_store:
377
+ # for store, the instance type is `algorithm-store` which contains an additional
378
+ # hyphen
379
+ return "-".join(helm_release_name.split("-")[1:-3])
380
+ else:
381
+ return "-".join(helm_release_name.split("-")[1:-2])
382
+
383
+
384
+ def extract_name_and_is_sandbox(name: str | None, is_sandbox: bool) -> tuple[str, bool]:
385
+ """
386
+ Extract the name and is_sandbox from the name.
387
+
388
+ Note that the name may be None: this occurs before when this function is called
389
+ before the user has selected a name. This scenario is fine because when the user
390
+ selects a name interactively, the name never ends with the .sandbox suffix.
391
+
392
+ Parameters
393
+ ----------
394
+ name : str | None
395
+ The name of the instance
396
+ is_sandbox : bool
397
+ Whether the instance is a sandbox instance
398
+
399
+ Returns
400
+ -------
401
+ tuple[str, bool]
402
+ The name and is_sandbox
403
+ """
404
+ if name and name.endswith(SANDBOX_SUFFIX):
405
+ return name[: -len(SANDBOX_SUFFIX)], True
406
+ else:
407
+ return name, is_sandbox
@@ -0,0 +1,82 @@
1
+ from vantage6.common import error
2
+ from vantage6.common.globals import InstanceType
3
+
4
+ from vantage6.cli.common.utils import (
5
+ find_running_service_names,
6
+ get_config_name_from_helm_release_name,
7
+ select_context_and_namespace,
8
+ select_running_service,
9
+ )
10
+ from vantage6.cli.context import get_context
11
+ from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
12
+ from vantage6.cli.context.node import NodeContext
13
+ from vantage6.cli.context.server import ServerContext
14
+
15
+
16
+ def get_and_select_ctx(
17
+ instance_type: InstanceType,
18
+ name: str,
19
+ system_folders: bool,
20
+ context: str,
21
+ namespace: str,
22
+ is_sandbox: bool,
23
+ ) -> ServerContext | NodeContext | AlgorithmStoreContext:
24
+ """
25
+ Get and select the context for the given instance type.
26
+
27
+ Parameters
28
+ ----------
29
+ instance_type : InstanceType
30
+ The type of instance to get the context for
31
+ name : str
32
+ The name of the instance
33
+ system_folders : bool
34
+ Whether to use system folders or not
35
+ context : str
36
+ The Kubernetes context to use
37
+ namespace : str
38
+ The Kubernetes namespace to use
39
+ is_sandbox : bool
40
+ Whether the configuration is a sandbox configuration
41
+
42
+ Returns
43
+ -------
44
+ ServerContext | NodeContext | AlgorithmStoreContext
45
+ The context for the given instance type
46
+ """
47
+ context, namespace = select_context_and_namespace(
48
+ context=context,
49
+ namespace=namespace,
50
+ )
51
+ running_services = find_running_service_names(
52
+ instance_type=instance_type,
53
+ only_system_folders=False,
54
+ only_user_folders=False,
55
+ context=context,
56
+ namespace=namespace,
57
+ sandbox=is_sandbox,
58
+ )
59
+
60
+ if not running_services:
61
+ error(f"No running {instance_type.value}s found.")
62
+ exit(1)
63
+
64
+ if not name:
65
+ helm_name = select_running_service(running_services, instance_type)
66
+
67
+ service_name = get_config_name_from_helm_release_name(
68
+ helm_name, is_store=(instance_type == InstanceType.ALGORITHM_STORE)
69
+ )
70
+ ctx = get_context(
71
+ instance_type, service_name, system_folders, is_sandbox=is_sandbox
72
+ )
73
+
74
+ else:
75
+ ctx = get_context(instance_type, name, system_folders, is_sandbox=is_sandbox)
76
+ helm_name = ctx.helm_release_name
77
+ service_name = ctx.name
78
+
79
+ if helm_name not in running_services:
80
+ error(f"The {instance_type.value} {service_name} is not running.")
81
+ exit(1)
82
+ return ctx
@@ -13,6 +13,7 @@ from colorama import Fore, Style
13
13
  from vantage6.common import error
14
14
  from vantage6.common.globals import InstanceType
15
15
 
16
+ from vantage6.cli.common.utils import extract_name_and_is_sandbox
16
17
  from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
17
18
  from vantage6.cli.context.auth import AuthContext
18
19
  from vantage6.cli.context.node import NodeContext
@@ -74,6 +75,8 @@ def get_context(
74
75
  AppContext
75
76
  Specialized subclass context of AppContext for the given instance type
76
77
  """
78
+ name, is_sandbox = extract_name_and_is_sandbox(name, is_sandbox)
79
+
77
80
  ctx_class = select_context_class(type_)
78
81
  if not ctx_class.config_exists(name, system_folders, is_sandbox=is_sandbox):
79
82
  scope = "system" if system_folders else "user"
@@ -117,7 +117,7 @@ class AlgorithmStoreContext(BaseServerContext):
117
117
 
118
118
  @classmethod
119
119
  def available_configurations(
120
- cls, system_folders: bool = S_FOL
120
+ cls, system_folders: bool = S_FOL, is_sandbox: bool = False
121
121
  ) -> tuple[list, list]:
122
122
  """
123
123
  Find all available server configurations in the default folders.
@@ -134,5 +134,5 @@ class AlgorithmStoreContext(BaseServerContext):
134
134
  list contains invalid configuration files.
135
135
  """
136
136
  return super().available_configurations(
137
- InstanceType.ALGORITHM_STORE, system_folders
137
+ InstanceType.ALGORITHM_STORE, system_folders, is_sandbox
138
138
  )
@@ -2,13 +2,37 @@ import click
2
2
 
3
3
  from vantage6.common import info
4
4
 
5
- from vantage6.cli.common.utils import attach_logs
5
+ from vantage6.cli.common.attach import attach_logs
6
+ from vantage6.cli.context import InstanceType
7
+ from vantage6.cli.globals import InfraComponentName
6
8
 
7
9
 
8
10
  @click.command()
9
- def cli_node_attach() -> None:
11
+ @click.option("-n", "--name", default=None, help="Name of the configuration")
12
+ @click.option("--system", "system_folders", flag_value=True, help="Use system folders")
13
+ @click.option("--user", "system_folders", flag_value=False, help="Use user folders")
14
+ @click.option("--context", default=None, help="Kubernetes context to use")
15
+ @click.option("--namespace", default=None, help="Kubernetes namespace to use")
16
+ @click.option(
17
+ "--sandbox", "is_sandbox", flag_value=True, help="Attach to a sandbox environment"
18
+ )
19
+ def cli_node_attach(
20
+ name: str | None,
21
+ system_folders: bool,
22
+ context: str,
23
+ namespace: str,
24
+ is_sandbox: bool,
25
+ ) -> None:
10
26
  """
11
27
  Show the node logs in the current console.
12
28
  """
13
29
  info("Attaching to node logs...")
14
- attach_logs("app=node")
30
+ attach_logs(
31
+ name,
32
+ instance_type=InstanceType.NODE,
33
+ infra_component=InfraComponentName.NODE,
34
+ system_folders=system_folders,
35
+ context=context,
36
+ namespace=namespace,
37
+ is_sandbox=is_sandbox,
38
+ )
vantage6/cli/node/list.py CHANGED
@@ -1,13 +1,8 @@
1
1
  import click
2
- import docker
3
- from colorama import Fore, Style
4
2
 
5
- from vantage6.common import warning
6
- from vantage6.common.docker.addons import check_docker_running
7
- from vantage6.common.globals import APPNAME
3
+ from vantage6.common.globals import InstanceType
8
4
 
9
- from vantage6.cli.context.node import NodeContext
10
- from vantage6.cli.node.common import find_running_node_names
5
+ from vantage6.cli.common.list import get_configuration_list
11
6
 
12
7
 
13
8
  @click.command()
@@ -18,40 +13,4 @@ def cli_node_list() -> None:
18
13
  Note that this command cannot find node configuration files in custom
19
14
  directories.
20
15
  """
21
-
22
- check_docker_running()
23
- client = docker.from_env()
24
-
25
- running_node_names = find_running_node_names(client)
26
-
27
- header = "\nName" + (21 * " ") + "Status" + (10 * " ") + "System/User"
28
-
29
- click.echo(header)
30
- click.echo("-" * len(header))
31
-
32
- running = Fore.GREEN + "Running" + Style.RESET_ALL
33
- stopped = Fore.RED + "Not running" + Style.RESET_ALL
34
-
35
- # system folders
36
- configs, f1 = NodeContext.available_configurations(system_folders=True)
37
- for config in configs:
38
- status = (
39
- running
40
- if f"{APPNAME}-{config.name}-system" in running_node_names
41
- else stopped
42
- )
43
- click.echo(f"{config.name:25}{status:25}System ")
44
-
45
- # user folders
46
- configs, f2 = NodeContext.available_configurations(system_folders=False)
47
- for config in configs:
48
- status = (
49
- running
50
- if f"{APPNAME}-{config.name}-user" in running_node_names
51
- else stopped
52
- )
53
- click.echo(f"{config.name:25}{status:25}User ")
54
-
55
- click.echo("-" * 53)
56
- if len(f1) + len(f2):
57
- warning(f"{Fore.RED}Failed imports: {len(f1) + len(f2)}{Style.RESET_ALL}")
16
+ get_configuration_list(InstanceType.NODE)
@@ -3,6 +3,7 @@ import click
3
3
  from vantage6.common import info
4
4
  from vantage6.common.globals import InstanceType
5
5
 
6
+ from vantage6.cli.common.attach import attach_logs
6
7
  from vantage6.cli.common.decorator import click_insert_context
7
8
  from vantage6.cli.common.start import (
8
9
  helm_install,
@@ -10,12 +11,11 @@ from vantage6.cli.common.start import (
10
11
  start_port_forward,
11
12
  )
12
13
  from vantage6.cli.common.utils import (
13
- attach_logs,
14
14
  create_directory_if_not_exists,
15
15
  select_context_and_namespace,
16
16
  )
17
17
  from vantage6.cli.context.node import NodeContext
18
- from vantage6.cli.globals import ChartName
18
+ from vantage6.cli.globals import ChartName, InfraComponentName
19
19
 
20
20
  from vantage6.node.globals import DEFAULT_PROXY_SERVER_PORT
21
21
 
@@ -123,4 +123,12 @@ def cli_node_start(
123
123
  )
124
124
 
125
125
  if attach:
126
- attach_logs("app=node")
126
+ attach_logs(
127
+ name,
128
+ instance_type=InstanceType.NODE,
129
+ infra_component=InfraComponentName.NODE,
130
+ system_folders=system_folders,
131
+ context=context,
132
+ namespace=namespace,
133
+ is_sandbox=ctx.is_sandbox,
134
+ )
vantage6/cli/node/stop.py CHANGED
@@ -1,20 +1,23 @@
1
1
  import click
2
- from kubernetes import client as k8s_client, config as k8s_config
2
+ from kubernetes import client as k8s_client
3
3
  from kubernetes.client import ApiException
4
- from kubernetes.config.config_exception import ConfigException
5
4
 
6
5
  from vantage6.common import error, info, warning
7
6
  from vantage6.common.globals import APPNAME, InstanceType
8
7
 
9
8
  from vantage6.cli.common.stop import execute_stop, helm_uninstall, stop_port_forward
10
- from vantage6.cli.common.utils import get_config_name_from_helm_release_name
9
+ from vantage6.cli.common.utils import (
10
+ extract_name_and_is_sandbox,
11
+ get_config_name_from_helm_release_name,
12
+ )
11
13
  from vantage6.cli.context import get_context
12
14
  from vantage6.cli.context.node import NodeContext
13
15
  from vantage6.cli.globals import (
14
16
  DEFAULT_NODE_SYSTEM_FOLDERS as N_FOL,
15
17
  InfraComponentName,
16
18
  )
17
- from vantage6.cli.node.task_cleanup import delete_job_related_pods
19
+ from vantage6.cli.node.common.task_cleanup import delete_job_related_pods
20
+ from vantage6.cli.utils_kubernetes import create_kubernetes_apis_with_ssl_handling
18
21
 
19
22
 
20
23
  @click.command()
@@ -50,6 +53,7 @@ def cli_node_stop(
50
53
  """
51
54
  Stop one or all running nodes.
52
55
  """
56
+ name, is_sandbox = extract_name_and_is_sandbox(name, is_sandbox)
53
57
  execute_stop(
54
58
  stop_function=_stop_node,
55
59
  stop_function_args={"system_folders": system_folders, "is_sandbox": is_sandbox},
@@ -142,18 +146,12 @@ def cleanup_task_jobs(
142
146
  error("Either all_nodes or node_ctx must be given to cleanup task jobs")
143
147
  return False
144
148
 
145
- # Load Kubernetes configuration (in-cluster first, fallback to kubeconfig)
149
+ # Load Kubernetes configuration with SSL handling
146
150
  try:
147
- k8s_config.load_incluster_config()
148
- except ConfigException:
149
- try:
150
- k8s_config.load_kube_config()
151
- except ConfigException as exc:
152
- error(f"Failed to load Kubernetes configuration: {exc}")
153
- return False
154
-
155
- core_api = k8s_client.CoreV1Api()
156
- batch_api = k8s_client.BatchV1Api()
151
+ core_api, batch_api = create_kubernetes_apis_with_ssl_handling()
152
+ except RuntimeError as exc:
153
+ error(f"Failed to load Kubernetes configuration: {exc}")
154
+ return False
157
155
 
158
156
  jobs = _get_jobs(namespace, batch_api)
159
157
 
@@ -1,14 +1,16 @@
1
1
  import click
2
- import docker
3
- import questionary as q
2
+ from kubernetes import config as k8s_config
3
+ from kubernetes.config.config_exception import ConfigException
4
+ from kubernetes.stream import stream
4
5
 
5
- from vantage6.common import error
6
- from vantage6.common.docker.addons import check_docker_running
7
- from vantage6.common.globals import APPNAME
6
+ from vantage6.common import error, info
7
+ from vantage6.common.globals import APPNAME, InstanceType
8
8
 
9
9
  from vantage6.cli import __version__
10
+ from vantage6.cli.common.utils import select_context_and_namespace
11
+ from vantage6.cli.common.version import get_and_select_ctx
10
12
  from vantage6.cli.globals import DEFAULT_NODE_SYSTEM_FOLDERS as N_FOL
11
- from vantage6.cli.node.common import find_running_node_names
13
+ from vantage6.cli.utils_kubernetes import get_core_api_with_ssl_handling
12
14
 
13
15
 
14
16
  @click.command()
@@ -27,36 +29,97 @@ from vantage6.cli.node.common import find_running_node_names
27
29
  help="Search for configuration in user folders rather than "
28
30
  "system folders. This is the default",
29
31
  )
30
- def cli_node_version(name: str, system_folders: bool) -> None:
32
+ @click.option("--context", default=None, help="Kubernetes context to use")
33
+ @click.option("--namespace", default=None, help="Kubernetes namespace to use")
34
+ @click.option(
35
+ "--sandbox", "is_sandbox", flag_value=True, help="Is this a sandbox environment?"
36
+ )
37
+ def cli_node_version(
38
+ name: str, system_folders: bool, context: str, namespace: str, is_sandbox: bool
39
+ ) -> None:
31
40
  """
32
41
  Returns current version of a vantage6 node.
33
42
  """
34
- check_docker_running()
35
- client = docker.from_env()
43
+ context, namespace = select_context_and_namespace(
44
+ context=context,
45
+ namespace=namespace,
46
+ )
47
+ ctx = get_and_select_ctx(
48
+ InstanceType.NODE, name, system_folders, context, namespace, is_sandbox
49
+ )
50
+ version = _get_node_version_from_k8s(ctx.helm_release_name, namespace, context)
51
+ info("")
52
+ info(f"Node version: {version}")
53
+ info(f"CLI version: {__version__}")
36
54
 
37
- running_node_names = find_running_node_names(client)
38
55
 
39
- if not name:
40
- if not running_node_names:
41
- error(
42
- "No nodes are running! You can only check the version for "
43
- "nodes that are running"
44
- )
45
- exit(1)
56
+ def _get_node_version_from_k8s(
57
+ helm_release: str,
58
+ namespace: str,
59
+ context: str,
60
+ ) -> str:
61
+ """
62
+ Runs 'vnode-local version' in the node pod belonging to the Helm release.
63
+ """
64
+ pod = _get_pod_name_for_helm_release(helm_release, namespace, context)
65
+ output = _exec_pod_command(
66
+ pod_name=pod,
67
+ namespace=namespace,
68
+ command=["vnode-local", "version"],
69
+ )
70
+ return output.strip()
71
+
72
+
73
+ def _get_pod_name_for_helm_release(
74
+ helm_release: str,
75
+ namespace: str,
76
+ context: str,
77
+ ) -> str:
78
+ """
79
+ Returns the first pod name for a given Helm release in a namespace.
80
+ Looks up pods using the standard Helm label 'app.kubernetes.io/instance'.
81
+ """
82
+ try:
83
+ # Load kubeconfig (context optional). Falls back to in-cluster if not available.
46
84
  try:
47
- name = q.select(
48
- "Select the node you wish to inspect:", choices=running_node_names
49
- ).unsafe_ask()
50
- except KeyboardInterrupt:
51
- error("Aborted by user!")
52
- return
53
- else:
54
- post_fix = "system" if system_folders else "user"
55
- name = f"{APPNAME}-{name}-{post_fix}"
56
-
57
- if name in running_node_names:
58
- container = client.containers.get(name)
59
- version = container.exec_run(cmd="vnode-local version", stdout=True)
60
- click.echo({"node": version.output.decode("utf-8"), "cli": __version__})
61
- else:
62
- error(f"Node {name} is not running! Cannot provide version...")
85
+ k8s_config.load_kube_config(context=context) # desktop/dev
86
+ except ConfigException:
87
+ k8s_config.load_incluster_config() # in-cluster
88
+ except ConfigException as exc:
89
+ raise RuntimeError(f"Failed to load Kubernetes config: {exc}") from exc
90
+
91
+ core = get_core_api_with_ssl_handling()
92
+ selector = f"app={APPNAME}-node,release={helm_release}"
93
+ pods = core.list_namespaced_pod(namespace=namespace, label_selector=selector).items
94
+ if not pods:
95
+ error(f"No pods found for Helm release '{helm_release}' in ns '{namespace}'")
96
+ exit(1)
97
+ # Prefer a Ready pod
98
+ for p in pods:
99
+ for cond in p.status.conditions or []:
100
+ if cond.type == "Ready" and cond.status == "True":
101
+ return p.metadata.name
102
+ # Fallback to first pod
103
+ return pods[0].metadata.name
104
+
105
+
106
+ def _exec_pod_command(
107
+ pod_name: str,
108
+ namespace: str,
109
+ command: list[str],
110
+ ) -> str:
111
+ """
112
+ Executes a command inside the specified pod (and optional container) and returns stdout.
113
+ """
114
+ core = get_core_api_with_ssl_handling()
115
+ resp = stream(
116
+ core.connect_get_namespaced_pod_exec,
117
+ pod_name,
118
+ namespace,
119
+ command=command,
120
+ stderr=True,
121
+ stdin=False,
122
+ stdout=True,
123
+ tty=False,
124
+ )
125
+ return resp
@@ -49,7 +49,10 @@ class BaseSandboxConfigManager:
49
49
  return {}
50
50
 
51
51
  def _create_and_get_data_dir(
52
- self, instance_type: InstanceType, is_data_folder: bool = False
52
+ self,
53
+ instance_type: InstanceType,
54
+ is_data_folder: bool = False,
55
+ node_name: str | None = None,
53
56
  ) -> Path:
54
57
  """
55
58
  Create and get the data directory.
@@ -61,6 +64,8 @@ class BaseSandboxConfigManager:
61
64
  is_data_folder: bool
62
65
  Whether or not to create the data folder or a config folder. This is only
63
66
  used for node databases. Default is False.
67
+ node_name: str | None
68
+ Name of the node. Only used if is_data_folder is True.
64
69
 
65
70
  Returns
66
71
  -------
@@ -83,7 +88,10 @@ class BaseSandboxConfigManager:
83
88
  data_dir = main_data_dir / self.server_name / "store"
84
89
  elif instance_type == InstanceType.NODE:
85
90
  if is_data_folder:
86
- last_subfolder = "data"
91
+ if node_name:
92
+ last_subfolder = f"data_{node_name}"
93
+ else:
94
+ last_subfolder = "data"
87
95
  else:
88
96
  last_subfolder = "node"
89
97
  data_dir = main_data_dir / self.server_name / last_subfolder
@@ -118,7 +118,7 @@ class CoreSandboxConfigManager(BaseSandboxConfigManager):
118
118
  # TODO make this configurable
119
119
  "image": (
120
120
  self.server_image
121
- or "harbor2.vantage6.ai/infrastructure/server:5.0.0a36"
121
+ or "harbor2.vantage6.ai/infrastructure/server:5.0.0a37"
122
122
  ),
123
123
  "algorithm_stores": [
124
124
  {
@@ -156,7 +156,7 @@ class CoreSandboxConfigManager(BaseSandboxConfigManager):
156
156
  # TODO: v5+ set to latest v5 image
157
157
  # TODO: make this configurable
158
158
  "image": (
159
- self.ui_image or "harbor2.vantage6.ai/infrastructure/ui:5.0.0a36"
159
+ self.ui_image or "harbor2.vantage6.ai/infrastructure/ui:5.0.0a37"
160
160
  ),
161
161
  },
162
162
  }
@@ -231,7 +231,7 @@ class CoreSandboxConfigManager(BaseSandboxConfigManager):
231
231
  "vantage6ServerUri": f"{HTTP_LOCALHOST}:{self.server_port}",
232
232
  "image": (
233
233
  self.store_image
234
- or "harbor2.vantage6.ai/infrastructure/algorithm-store:5.0.0a36"
234
+ or "harbor2.vantage6.ai/infrastructure/algorithm-store:5.0.0a37"
235
235
  ),
236
236
  "keycloakUrl": (
237
237
  f"http://vantage6-{self.server_name}-auth-user-auth-keycloak."