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.
- vantage6/cli/algorithm/generate_algorithm_json.py +2 -1
- vantage6/cli/algostore/attach.py +28 -3
- vantage6/cli/algostore/list.py +2 -2
- vantage6/cli/algostore/start.py +11 -3
- vantage6/cli/algostore/version.py +62 -0
- vantage6/cli/auth/attach.py +1 -1
- vantage6/cli/auth/list.py +2 -2
- vantage6/cli/auth/remove.py +58 -0
- vantage6/cli/auth/start.py +12 -8
- vantage6/cli/cli.py +2 -0
- vantage6/cli/common/attach.py +114 -0
- vantage6/cli/common/decorator.py +9 -6
- vantage6/cli/common/list.py +68 -0
- vantage6/cli/common/new.py +3 -2
- vantage6/cli/common/remove.py +18 -0
- vantage6/cli/common/stop.py +6 -11
- vantage6/cli/common/utils.py +44 -76
- vantage6/cli/common/version.py +82 -0
- vantage6/cli/configuration_create.py +3 -2
- vantage6/cli/context/__init__.py +3 -0
- vantage6/cli/context/algorithm_store.py +2 -2
- vantage6/cli/node/attach.py +27 -3
- vantage6/cli/node/common/__init__.py +31 -6
- vantage6/cli/node/create_private_key.py +11 -10
- vantage6/cli/node/list.py +3 -44
- vantage6/cli/node/set_api_key.py +12 -6
- vantage6/cli/node/start.py +11 -3
- vantage6/cli/node/stop.py +13 -15
- vantage6/cli/node/version.py +96 -33
- vantage6/cli/sandbox/config/base.py +10 -2
- vantage6/cli/sandbox/config/core.py +3 -3
- vantage6/cli/sandbox/config/node.py +2 -5
- vantage6/cli/sandbox/remove.py +17 -35
- vantage6/cli/sandbox/start.py +8 -0
- vantage6/cli/sandbox/stop.py +1 -1
- vantage6/cli/server/attach.py +28 -3
- vantage6/cli/server/import_.py +85 -17
- vantage6/cli/server/list.py +2 -2
- vantage6/cli/server/start.py +11 -3
- vantage6/cli/server/version.py +31 -18
- vantage6/cli/template/algo_store_config.j2 +3 -0
- vantage6/cli/template/node_config.j2 +3 -1
- vantage6/cli/use/context.py +8 -1
- vantage6/cli/use/namespace.py +10 -7
- vantage6/cli/utils_kubernetes.py +270 -0
- {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a40.dist-info}/METADATA +3 -3
- {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a40.dist-info}/RECORD +50 -45
- /vantage6/cli/node/{task_cleanup/__init__.py → common/task_cleanup.py} +0 -0
- {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a40.dist-info}/WHEEL +0 -0
- {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a40.dist-info}/entry_points.txt +0 -0
vantage6/cli/node/version.py
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import click
|
|
2
|
-
import
|
|
3
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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),
|
vantage6/cli/sandbox/remove.py
CHANGED
|
@@ -4,19 +4,18 @@ from shutil import rmtree
|
|
|
4
4
|
|
|
5
5
|
import click
|
|
6
6
|
|
|
7
|
-
from vantage6.common import error,
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
vantage6/cli/sandbox/start.py
CHANGED
|
@@ -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,
|
vantage6/cli/sandbox/stop.py
CHANGED
vantage6/cli/server/attach.py
CHANGED
|
@@ -2,13 +2,38 @@ import click
|
|
|
2
2
|
|
|
3
3
|
from vantage6.common import info
|
|
4
4
|
|
|
5
|
-
from vantage6.cli.common.
|
|
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
|
-
|
|
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(
|
|
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
|
+
)
|
vantage6/cli/server/import_.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
#
|
|
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
|
|
95
|
-
address2=organization
|
|
96
|
-
zipcode=organization
|
|
97
|
-
country=organization
|
|
98
|
-
domain=organization
|
|
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
|
-
#
|
|
156
|
-
|
|
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)
|
vantage6/cli/server/list.py
CHANGED
|
@@ -2,7 +2,7 @@ import click
|
|
|
2
2
|
|
|
3
3
|
from vantage6.common.globals import InstanceType
|
|
4
4
|
|
|
5
|
-
from vantage6.cli.common.
|
|
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
|
-
|
|
13
|
+
get_configuration_list(InstanceType.SERVER)
|
vantage6/cli/server/start.py
CHANGED
|
@@ -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(
|
|
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
|
+
)
|
vantage6/cli/server/version.py
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import click
|
|
2
|
-
import
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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__}")
|