vantage6 5.0.0a37__py3-none-any.whl → 5.0.0a40__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 (50) hide show
  1. vantage6/cli/algorithm/generate_algorithm_json.py +2 -1
  2. vantage6/cli/algostore/attach.py +28 -3
  3. vantage6/cli/algostore/list.py +2 -2
  4. vantage6/cli/algostore/start.py +11 -3
  5. vantage6/cli/algostore/version.py +62 -0
  6. vantage6/cli/auth/attach.py +1 -1
  7. vantage6/cli/auth/list.py +2 -2
  8. vantage6/cli/auth/remove.py +58 -0
  9. vantage6/cli/auth/start.py +12 -8
  10. vantage6/cli/cli.py +2 -0
  11. vantage6/cli/common/attach.py +114 -0
  12. vantage6/cli/common/decorator.py +9 -6
  13. vantage6/cli/common/list.py +68 -0
  14. vantage6/cli/common/new.py +3 -2
  15. vantage6/cli/common/remove.py +18 -0
  16. vantage6/cli/common/stop.py +6 -11
  17. vantage6/cli/common/utils.py +44 -76
  18. vantage6/cli/common/version.py +82 -0
  19. vantage6/cli/configuration_create.py +3 -2
  20. vantage6/cli/context/__init__.py +3 -0
  21. vantage6/cli/context/algorithm_store.py +2 -2
  22. vantage6/cli/node/attach.py +27 -3
  23. vantage6/cli/node/common/__init__.py +31 -6
  24. vantage6/cli/node/create_private_key.py +11 -10
  25. vantage6/cli/node/list.py +3 -44
  26. vantage6/cli/node/set_api_key.py +12 -6
  27. vantage6/cli/node/start.py +11 -3
  28. vantage6/cli/node/stop.py +13 -15
  29. vantage6/cli/node/version.py +96 -33
  30. vantage6/cli/sandbox/config/base.py +10 -2
  31. vantage6/cli/sandbox/config/core.py +3 -3
  32. vantage6/cli/sandbox/config/node.py +2 -5
  33. vantage6/cli/sandbox/remove.py +17 -35
  34. vantage6/cli/sandbox/start.py +8 -0
  35. vantage6/cli/sandbox/stop.py +1 -1
  36. vantage6/cli/server/attach.py +28 -3
  37. vantage6/cli/server/import_.py +85 -17
  38. vantage6/cli/server/list.py +2 -2
  39. vantage6/cli/server/start.py +11 -3
  40. vantage6/cli/server/version.py +31 -18
  41. vantage6/cli/template/algo_store_config.j2 +3 -0
  42. vantage6/cli/template/node_config.j2 +3 -1
  43. vantage6/cli/use/context.py +8 -1
  44. vantage6/cli/use/namespace.py +10 -7
  45. vantage6/cli/utils_kubernetes.py +270 -0
  46. {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a40.dist-info}/METADATA +3 -3
  47. {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a40.dist-info}/RECORD +50 -45
  48. /vantage6/cli/node/{task_cleanup/__init__.py → common/task_cleanup.py} +0 -0
  49. {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a40.dist-info}/WHEEL +0 -0
  50. {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a40.dist-info}/entry_points.txt +0 -0
@@ -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."
@@ -214,7 +214,7 @@ class NodeSandboxConfigManager(BaseSandboxConfigManager):
214
214
  config_name = f"{self.server_name}-{node_name}"
215
215
 
216
216
  path_to_data_dir = self._create_and_get_data_dir(
217
- InstanceType.NODE, is_data_folder=True
217
+ InstanceType.NODE, is_data_folder=True, node_name=node_name
218
218
  )
219
219
 
220
220
  # delete old node config if it exists
@@ -262,7 +262,7 @@ class NodeSandboxConfigManager(BaseSandboxConfigManager):
262
262
  "image": (
263
263
  self.node_image
264
264
  # TODO v5+ update
265
- or "harbor2.vantage6.ai/infrastructure/node:5.0.0a36"
265
+ or "harbor2.vantage6.ai/infrastructure/node:5.0.0a37"
266
266
  ),
267
267
  "logging": {
268
268
  "level": "DEBUG",
@@ -272,9 +272,6 @@ class NodeSandboxConfigManager(BaseSandboxConfigManager):
272
272
  f"http://vantage6-{self.server_name}-auth-user-auth-keycloak."
273
273
  f"{self.namespace}.svc.cluster.local"
274
274
  ),
275
- "dev": {
276
- "task_dir_extension": str(path_to_data_dir),
277
- },
278
275
  "persistence": {
279
276
  "tasks": {
280
277
  "hostPath": str(path_to_data_dir),
@@ -4,19 +4,18 @@ from shutil import rmtree
4
4
 
5
5
  import click
6
6
 
7
- from vantage6.common import error, info
7
+ from vantage6.common import error, warning
8
8
  from vantage6.common.globals import InstanceType
9
9
 
10
+ from vantage6.cli.auth.remove import auth_remove
10
11
  from vantage6.cli.common.remove import execute_remove
11
12
  from vantage6.cli.configuration_create import select_configuration_questionnaire
12
13
  from vantage6.cli.context import get_context
13
14
  from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
14
15
  from vantage6.cli.context.auth import AuthContext
15
16
  from vantage6.cli.context.node import NodeContext
16
- from vantage6.cli.context.server import ServerContext
17
17
  from vantage6.cli.globals import InfraComponentName
18
18
  from vantage6.cli.server.remove import cli_server_remove
19
- from vantage6.cli.utils import remove_file
20
19
 
21
20
 
22
21
  @click.command()
@@ -60,28 +59,6 @@ def cli_sandbox_remove(
60
59
 
61
60
  ctx = get_context(InstanceType.SERVER, name, system_folders=False, is_sandbox=True)
62
61
 
63
- # remove the server
64
- # Note that this also checks if the server is running. Therefore, it is prevented
65
- # that a running sandbox is removed.
66
- for handler in itertools.chain(ctx.log.handlers, ctx.log.root.handlers):
67
- handler.close()
68
- click_ctx.invoke(
69
- cli_server_remove, ctx=ctx, name=name, system_folders=False, force=True
70
- )
71
-
72
- # removing the server import config
73
- info("Deleting demo import config file")
74
- server_configs = ServerContext.instance_folders(
75
- InstanceType.SERVER, ctx.name, system_folders=False
76
- )
77
- import_config_to_del = Path(server_configs["dev"]) / f"{ctx.name}.yaml"
78
- remove_file(import_config_to_del, "import_configuration")
79
-
80
- # also remove the server folder
81
- server_folder = server_configs["data"]
82
- if server_folder.is_dir():
83
- rmtree(server_folder)
84
-
85
62
  # remove the store folder
86
63
  store_configs = AlgorithmStoreContext.instance_folders(
87
64
  InstanceType.ALGORITHM_STORE,
@@ -117,20 +94,13 @@ def cli_sandbox_remove(
117
94
  if auth_folder.is_dir():
118
95
  rmtree(auth_folder)
119
96
 
120
- # remove the auth config file
97
+ # remove the auth service
121
98
  auth_ctx = AuthContext(
122
99
  instance_name=f"{ctx.name}-auth",
123
100
  system_folders=False,
124
101
  is_sandbox=True,
125
102
  )
126
- execute_remove(
127
- auth_ctx,
128
- InstanceType.AUTH,
129
- InfraComponentName.AUTH,
130
- f"{ctx.name}-auth",
131
- system_folders=False,
132
- force=True,
133
- )
103
+ auth_remove(auth_ctx, f"{ctx.name}-auth", system_folders=False, force=True)
134
104
 
135
105
  # remove the nodes
136
106
  NodeContext.LOGGING_ENABLED = False
@@ -168,6 +138,18 @@ def cli_sandbox_remove(
168
138
 
169
139
  # remove data files attached to the network
170
140
  data_dirs_nodes = NodeContext.instance_folders("node", "", False)["dev"]
171
- rmtree(Path(data_dirs_nodes / ctx.name))
141
+ try:
142
+ rmtree(Path(data_dirs_nodes / ctx.name))
143
+ except Exception as e:
144
+ warning(f"Failed to delete data directory {data_dirs_nodes / ctx.name}: {e}")
172
145
 
146
+ # remove the server last - if anything goes wrong, the server is still there so the
147
+ # user can still retry the removal.
148
+ # Note that this also checks if the server is running. Therefore, it is prevented
149
+ # that a running sandbox is removed.
150
+ for handler in itertools.chain(ctx.log.handlers, ctx.log.root.handlers):
151
+ handler.close()
152
+ click_ctx.invoke(
153
+ cli_server_remove, ctx=ctx, name=name, system_folders=False, force=True
154
+ )
173
155
  # TODO remove the right data in the custom data directory if it is provided
@@ -44,6 +44,12 @@ from vantage6.cli.server.start import cli_server_start
44
44
  help="Generate this number of nodes in the development network. Only used if "
45
45
  "--re-initialize flag is provided.",
46
46
  )
47
+ @click.option(
48
+ "--node-image",
49
+ type=str,
50
+ default=None,
51
+ help="Node image to use. Only used if --re-initialize flag is provided.",
52
+ )
47
53
  @click.option(
48
54
  "--extra-node-config",
49
55
  type=click.Path("rb"),
@@ -80,6 +86,7 @@ def cli_sandbox_start(
80
86
  local_chart_dir: Path | None,
81
87
  re_initialize: bool,
82
88
  num_nodes: int,
89
+ node_image: str | None,
83
90
  extra_node_config: Path | None,
84
91
  add_dataset: tuple[str, Path] | None,
85
92
  custom_data_dir: Path | None,
@@ -101,6 +108,7 @@ def cli_sandbox_start(
101
108
  namespace=namespace,
102
109
  num_nodes=num_nodes,
103
110
  initialize=re_initialize,
111
+ node_image=node_image,
104
112
  extra_node_config=extra_node_config,
105
113
  add_dataset=add_dataset,
106
114
  custom_data_dir=custom_data_dir,
@@ -83,7 +83,7 @@ def cli_sandbox_stop(
83
83
  is_sandbox=True,
84
84
  )
85
85
 
86
- # TODO: stop the auth service
86
+ # stop the auth service
87
87
  cmd = [
88
88
  "v6",
89
89
  "auth",
@@ -2,13 +2,38 @@ 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_server_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_server_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 server logs in the current console.
12
28
  """
13
29
  info("Attaching to server logs...")
14
- attach_logs("app=vantage6-server", "component=vantage6-server")
30
+ attach_logs(
31
+ name,
32
+ instance_type=InstanceType.SERVER,
33
+ infra_component=InfraComponentName.SERVER,
34
+ system_folders=system_folders,
35
+ context=context,
36
+ namespace=namespace,
37
+ is_sandbox=is_sandbox,
38
+ additional_labels="component=vantage6-server",
39
+ )
@@ -1,6 +1,7 @@
1
1
  import click
2
2
  import requests
3
3
  import yaml
4
+ from marshmallow import Schema, ValidationError, fields, post_load
4
5
 
5
6
  from vantage6.common import error, info
6
7
  from vantage6.common.globals import (
@@ -64,7 +65,8 @@ def cli_server_import(ctx: ServerContext, file: str, drop_all: bool) -> None:
64
65
  info("Loading and validating import file")
65
66
  with open(file, "r") as f:
66
67
  import_data = yaml.safe_load(f)
67
- # TODO: validate import file
68
+
69
+ _check_import_file(import_data)
68
70
 
69
71
  client = UserClient(
70
72
  server_url=f"{ctx.config['server']['baseUrl']}{ctx.config['server']['apiPath']}",
@@ -77,7 +79,9 @@ def cli_server_import(ctx: ServerContext, file: str, drop_all: bool) -> None:
77
79
  info("Authenticate using admin credentials (opens browser for login)")
78
80
  client.authenticate()
79
81
 
80
- # TODO: validate that the user has the correct permissions to import data
82
+ # Note: we do not validate that the user has the correct permissions to import data.
83
+ # As the user has access to the `v6 server import` command, they already have
84
+ # access to the server+database.
81
85
 
82
86
  if drop_all:
83
87
  info("Dropping all existing data")
@@ -91,11 +95,12 @@ def cli_server_import(ctx: ServerContext, file: str, drop_all: bool) -> None:
91
95
  for organization in import_data["organizations"]:
92
96
  org = client.organization.create(
93
97
  name=organization["name"],
94
- address1=organization["address1"] or "",
95
- address2=organization["address2"] or "",
96
- zipcode=organization["zipcode"] or "",
97
- country=organization["country"] or "",
98
- domain=organization["domain"] or "",
98
+ address1=organization.get("address1", ""),
99
+ address2=organization.get("address2", ""),
100
+ zipcode=str(organization.get("zipcode", "")),
101
+ country=organization.get("country", ""),
102
+ domain=organization.get("domain", ""),
103
+ public_key=organization.get("public_key", ""),
99
104
  )
100
105
  organizations.append(org)
101
106
 
@@ -152,16 +157,79 @@ def _drop_all(client: UserClient) -> None:
152
157
  info(f"Deleting collaboration {collaboration['name']}")
153
158
  client.collaboration.delete(collaboration["id"], delete_dependents=True)
154
159
 
155
- # TODO: For some reason, the `delete_dependents` parameter is not working for users,
156
- # so we delete them here first.
157
- while users := [u for u in client.user.list()["data"] if u["username"] != "admin"]:
158
- for user in users:
159
- info(f"Deleting user {user['username']}")
160
- client.user.delete(user["id"])
161
-
162
- while orgs := [
163
- o for o in client.organization.list()["data"] if o["name"] != "root"
164
- ]:
160
+ # remove all organizations but keep the root organization
161
+ while orgs := [o for o in client.organization.list()["data"] if o["id"] != 1]:
165
162
  for org in orgs:
166
163
  info(f"Deleting organization {org['name']}")
167
164
  client.organization.delete(org["id"], delete_dependents=True)
165
+
166
+
167
+ class StrOrInt(fields.Field):
168
+ """Field that can be a string or an integer"""
169
+
170
+ def _serialize(self, value, attr, obj, **kwargs):
171
+ if isinstance(value, str):
172
+ return value
173
+ elif isinstance(value, int):
174
+ return str(value)
175
+ else:
176
+ raise ValidationError("Value must be a string or an integer")
177
+
178
+
179
+ class UserSchema(Schema):
180
+ username = fields.Str(required=True)
181
+ password = fields.Str(required=True)
182
+
183
+
184
+ class OrganizationSchema(Schema):
185
+ name = fields.Str(required=True)
186
+ address1 = fields.Str(missing="")
187
+ address2 = fields.Str(missing="")
188
+ zipcode = StrOrInt(missing="")
189
+ country = fields.Str(missing="")
190
+ domain = fields.Str(missing="")
191
+ public_key = fields.Str(missing="")
192
+ users = fields.List(fields.Nested(UserSchema), missing=[])
193
+
194
+
195
+ class ParticipantSchema(Schema):
196
+ name = fields.Str(required=True)
197
+
198
+
199
+ class CollaborationSchema(Schema):
200
+ name = fields.Str(required=True)
201
+ participants = fields.List(fields.Nested(ParticipantSchema), required=True)
202
+ encrypted = fields.Bool(missing=False)
203
+
204
+
205
+ class ImportDataSchema(Schema):
206
+ organizations = fields.List(fields.Nested(OrganizationSchema), missing=[])
207
+ collaborations = fields.List(fields.Nested(CollaborationSchema), missing=[])
208
+
209
+ @post_load
210
+ def validate_participants_exist(self, data, **kwargs):
211
+ """Validate that all participants exist in organizations"""
212
+ org_names = {org["name"] for org in data.get("organizations", [])}
213
+
214
+ for collaboration in data.get("collaborations", []):
215
+ for participant in collaboration.get("participants", []):
216
+ if participant["name"] not in org_names:
217
+ raise ValidationError(
218
+ f"Participant {participant['name']} not found in organizations",
219
+ field_name="collaborations",
220
+ )
221
+ return data
222
+
223
+
224
+ def _check_import_file(import_data: dict) -> dict:
225
+ """
226
+ Validate import file using Marshmallow schemas.
227
+ Returns the validated and cleaned data.
228
+ """
229
+ schema = ImportDataSchema()
230
+ try:
231
+ return schema.load(import_data)
232
+ except ValidationError as err:
233
+ # Handle validation errors gracefully
234
+ error(f"Validation error: {err.messages}")
235
+ exit(1)
@@ -2,7 +2,7 @@ import click
2
2
 
3
3
  from vantage6.common.globals import InstanceType
4
4
 
5
- from vantage6.cli.common.utils import get_server_configuration_list
5
+ from vantage6.cli.common.list import get_configuration_list
6
6
 
7
7
 
8
8
  @click.command()
@@ -10,4 +10,4 @@ def cli_server_configuration_list() -> None:
10
10
  """
11
11
  Print the available server configurations.
12
12
  """
13
- get_server_configuration_list(InstanceType.SERVER)
13
+ get_configuration_list(InstanceType.SERVER)
@@ -3,6 +3,7 @@ import click
3
3
  from vantage6.common import info
4
4
  from vantage6.common.globals import InstanceType, Ports
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.server import ServerContext
18
- from vantage6.cli.globals import ChartName
18
+ from vantage6.cli.globals import ChartName, InfraComponentName
19
19
 
20
20
 
21
21
  @click.command()
@@ -95,4 +95,12 @@ def cli_server_start(
95
95
  )
96
96
 
97
97
  if attach:
98
- attach_logs("app=vantage6-server", "component=vantage6-server")
98
+ attach_logs(
99
+ name,
100
+ instance_type=InstanceType.SERVER,
101
+ infra_component=InfraComponentName.SERVER,
102
+ system_folders=system_folders,
103
+ context=context,
104
+ namespace=namespace,
105
+ is_sandbox=ctx.is_sandbox,
106
+ )
@@ -1,12 +1,11 @@
1
1
  import click
2
- import docker
2
+ import requests
3
3
 
4
- from vantage6.common import error
5
- from vantage6.common.docker.addons import check_docker_running
4
+ from vantage6.common import error, info
6
5
  from vantage6.common.globals import InstanceType
7
6
 
8
7
  from vantage6.cli import __version__
9
- from vantage6.cli.common.utils import get_running_servers, get_server_name
8
+ from vantage6.cli.common.version import get_and_select_ctx
10
9
  from vantage6.cli.globals import DEFAULT_SERVER_SYSTEM_FOLDERS
11
10
 
12
11
 
@@ -16,22 +15,36 @@ from vantage6.cli.globals import DEFAULT_SERVER_SYSTEM_FOLDERS
16
15
  @click.option(
17
16
  "--user", "system_folders", flag_value=False, default=DEFAULT_SERVER_SYSTEM_FOLDERS
18
17
  )
19
- def cli_server_version(name: str, system_folders: bool) -> None:
18
+ @click.option("--context", default=None, help="Kubernetes context to use")
19
+ @click.option("--namespace", default=None, help="Kubernetes namespace to use")
20
+ @click.option(
21
+ "--sandbox", "is_sandbox", flag_value=True, help="Is this a sandbox environment?"
22
+ )
23
+ def cli_server_version(
24
+ name: str, system_folders: bool, context: str, namespace: str, is_sandbox: bool
25
+ ) -> None:
20
26
  """
21
27
  Print the version of the vantage6 server.
22
28
  """
23
- check_docker_running()
24
- client = docker.from_env()
25
-
26
- running_server_names = get_running_servers(client, InstanceType.SERVER)
27
-
28
- name = get_server_name(
29
- name, system_folders, running_server_names, InstanceType.SERVER
29
+ ctx = get_and_select_ctx(
30
+ InstanceType.SERVER, name, system_folders, context, namespace, is_sandbox
30
31
  )
32
+ server_config = ctx.config.get("server", {})
33
+ base_url = server_config.get("baseUrl", "")
34
+ api_path = server_config.get("apiPath", "")
35
+ if not base_url:
36
+ error("No base URL found in server configuration.")
37
+ return
38
+ if not api_path:
39
+ error("No API path found in server configuration.")
40
+ return
41
+
42
+ response = requests.get(f"{base_url}{api_path}/version")
43
+ if response.status_code != 200:
44
+ error("Failed to get server version.")
45
+ return
46
+ server_version = response.json().get("version", "")
31
47
 
32
- if name in running_server_names:
33
- container = client.containers.get(name)
34
- version = container.exec_run(cmd="vserver-local version", stdout=True)
35
- click.echo({"server": version.output.decode("utf-8"), "cli": __version__})
36
- else:
37
- error(f"Server {name} is not running! Cannot provide version...")
48
+ info("")
49
+ info(f"Server version: {server_version}")
50
+ info(f"CLI version: {__version__}")