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.
- 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 +5 -3
- vantage6/cli/common/list.py +68 -0
- vantage6/cli/common/remove.py +18 -0
- vantage6/cli/common/stop.py +3 -9
- vantage6/cli/common/utils.py +44 -76
- vantage6/cli/common/version.py +82 -0
- 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/list.py +3 -44
- 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/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 +2 -0
- 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.0a38.dist-info}/METADATA +3 -3
- {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a38.dist-info}/RECORD +43 -38
- /vantage6/cli/node/{task_cleanup/__init__.py → common/task_cleanup.py} +0 -0
- {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a38.dist-info}/WHEEL +0 -0
- {vantage6-5.0.0a37.dist-info → vantage6-5.0.0a38.dist-info}/entry_points.txt +0 -0
|
@@ -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/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__}")
|
|
@@ -102,6 +102,9 @@ store:
|
|
|
102
102
|
port: 7602
|
|
103
103
|
{% endif %}
|
|
104
104
|
|
|
105
|
+
# The port to expose the store on in the cluster
|
|
106
|
+
port: {{ store.port | default(7602) }}
|
|
107
|
+
|
|
105
108
|
logging:
|
|
106
109
|
# Controls the logging output level. Could be one of the following
|
|
107
110
|
# levels: CRITICAL, ERROR, WARNING, INFO, DEBUG, NOTSET
|
vantage6/cli/use/context.py
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import click
|
|
2
2
|
import questionary
|
|
3
|
-
from kubernetes import config
|
|
3
|
+
from kubernetes import client, config
|
|
4
4
|
|
|
5
5
|
from vantage6.common import error
|
|
6
6
|
|
|
7
7
|
from vantage6.cli.config import CliConfig
|
|
8
8
|
from vantage6.cli.utils import switch_context_and_namespace
|
|
9
|
+
from vantage6.cli.utils_kubernetes import configure_kubernetes_client_for_microk8s
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
@click.command()
|
|
@@ -14,6 +15,12 @@ def cli_use_context(context: str):
|
|
|
14
15
|
"""
|
|
15
16
|
Set which Kubernetes context to use.
|
|
16
17
|
"""
|
|
18
|
+
# Configure for MicroK8s if needed
|
|
19
|
+
config.load_kube_config()
|
|
20
|
+
cfg = client.Configuration.get_default_copy()
|
|
21
|
+
configure_kubernetes_client_for_microk8s(cfg)
|
|
22
|
+
client.Configuration.set_default(cfg)
|
|
23
|
+
|
|
17
24
|
# Get available contexts
|
|
18
25
|
contexts, active_context = config.list_kube_config_contexts()
|
|
19
26
|
context_names = [ctx["name"] for ctx in contexts]
|
vantage6/cli/use/namespace.py
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import click
|
|
2
2
|
import questionary
|
|
3
|
-
from kubernetes import client
|
|
3
|
+
from kubernetes import client
|
|
4
4
|
|
|
5
5
|
from vantage6.common import error
|
|
6
6
|
|
|
7
7
|
from vantage6.cli.config import CliConfig
|
|
8
8
|
from vantage6.cli.utils import switch_context_and_namespace
|
|
9
|
+
from vantage6.cli.utils_kubernetes import (
|
|
10
|
+
get_core_api_with_ssl_handling,
|
|
11
|
+
)
|
|
9
12
|
|
|
10
13
|
|
|
11
14
|
@click.command()
|
|
@@ -16,15 +19,15 @@ def cli_use_namespace(namespace: str):
|
|
|
16
19
|
|
|
17
20
|
The namespace will be created if it does not exist.
|
|
18
21
|
"""
|
|
19
|
-
#
|
|
20
|
-
|
|
22
|
+
# Configure for MicroK8s if needed
|
|
23
|
+
core_api, _ = get_core_api_with_ssl_handling()
|
|
21
24
|
|
|
22
25
|
try:
|
|
23
|
-
|
|
24
|
-
namespace_list = v1.list_namespace()
|
|
26
|
+
namespace_list = core_api.list_namespace()
|
|
25
27
|
except Exception:
|
|
26
28
|
error(
|
|
27
|
-
"Failed to connect to Kubernetes cluster. Check if the cluster is running
|
|
29
|
+
"Failed to connect to Kubernetes cluster. Check if the cluster is running "
|
|
30
|
+
"and reachable."
|
|
28
31
|
)
|
|
29
32
|
return
|
|
30
33
|
|
|
@@ -46,7 +49,7 @@ def cli_use_namespace(namespace: str):
|
|
|
46
49
|
namespace_body = client.V1Namespace(
|
|
47
50
|
metadata=client.V1ObjectMeta(name=namespace)
|
|
48
51
|
)
|
|
49
|
-
|
|
52
|
+
core_api.create_namespace(namespace_body)
|
|
50
53
|
|
|
51
54
|
# Switch to the selected namespace for current context
|
|
52
55
|
switch_context_and_namespace(namespace=namespace)
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kubernetes utility functions for Vantage6 CLI.
|
|
3
|
+
|
|
4
|
+
This module provides utilities for handling Kubernetes client configuration,
|
|
5
|
+
especially for MicroK8s environments that may have SSL certificate issues.
|
|
6
|
+
|
|
7
|
+
The issue is that the python kubernetes client does not automatically use the
|
|
8
|
+
certificate from microk8s, so it is manually configured in this module. Note that this
|
|
9
|
+
is only an issue for the CLI and not for running nodes/servers/stores/etc., because
|
|
10
|
+
those are started using the `kubectl` command, which includes the microk8scertificate
|
|
11
|
+
automatically.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import base64
|
|
15
|
+
import ssl
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from kubernetes import client, config
|
|
19
|
+
from kubernetes.config.config_exception import ConfigException
|
|
20
|
+
|
|
21
|
+
from vantage6.common import warning
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def configure_kubernetes_client_for_microk8s(cfg: client.Configuration) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Configure the Kubernetes client to handle MicroK8s SSL certificate issues.
|
|
27
|
+
|
|
28
|
+
This function detects if we're using MicroK8s and configures the client
|
|
29
|
+
to handle SSL certificates appropriately for both development and production.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
cfg : client.Configuration
|
|
34
|
+
The Kubernetes client configuration to configure
|
|
35
|
+
"""
|
|
36
|
+
# Check if we're using MicroK8s by looking at the current context
|
|
37
|
+
if is_microk8s_context():
|
|
38
|
+
_configure_microk8s_ssl(cfg)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _configure_microk8s_ssl(cfg: client.Configuration) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Configure SSL settings for MicroK8s.
|
|
44
|
+
|
|
45
|
+
This function handles MicroK8s SSL certificates in a secure way by:
|
|
46
|
+
1. First trying to use the MicroK8s certificate directly
|
|
47
|
+
2. If that fails, falling back to a more lenient but still secure approach
|
|
48
|
+
"""
|
|
49
|
+
try:
|
|
50
|
+
# Try to get the MicroK8s certificate and use it properly
|
|
51
|
+
if cert_path := _get_microk8s_certificate_path():
|
|
52
|
+
_configure_with_certificate(cert_path, cfg)
|
|
53
|
+
else:
|
|
54
|
+
warning(
|
|
55
|
+
"MicroK8s certificate not found. You may run into errors when using "
|
|
56
|
+
"the CLI."
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
except Exception as e:
|
|
60
|
+
warning(f"Could not configure MicroK8s SSL settings: {e}")
|
|
61
|
+
warning("You may run into errors when using the CLI.")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _get_microk8s_certificate_path() -> Path | None:
|
|
65
|
+
"""
|
|
66
|
+
Get the path to the MicroK8s certificate.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
Path | None
|
|
71
|
+
Path to the MicroK8s certificate if found, None otherwise
|
|
72
|
+
"""
|
|
73
|
+
# Common MicroK8s certificate locations
|
|
74
|
+
possible_paths = [
|
|
75
|
+
Path.home() / ".kube" / "microk8s.crt",
|
|
76
|
+
Path("/var/snap/microk8s/current/certs/ca.crt"),
|
|
77
|
+
Path("/var/snap/microk8s/current/certs/server.crt"),
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for cert_path in possible_paths:
|
|
81
|
+
if cert_path.exists():
|
|
82
|
+
return cert_path
|
|
83
|
+
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _configure_with_certificate(cert_path: Path, cfg: client.Configuration) -> None:
|
|
88
|
+
"""
|
|
89
|
+
Configure the Kubernetes client to use a specific certificate.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
cert_path : Path
|
|
94
|
+
Path to the certificate file
|
|
95
|
+
"""
|
|
96
|
+
try:
|
|
97
|
+
# Validate the certificate before using it
|
|
98
|
+
if not _validate_certificate(cert_path):
|
|
99
|
+
warning(
|
|
100
|
+
f"Certificate {cert_path} appears to be invalid. You may run into "
|
|
101
|
+
"errors when using the CLI."
|
|
102
|
+
)
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
cfg.verify_ssl = True
|
|
106
|
+
cfg.ssl_ca_cert = str(cert_path)
|
|
107
|
+
|
|
108
|
+
# Apply the configuration to the default client
|
|
109
|
+
client.Configuration.set_default(cfg)
|
|
110
|
+
|
|
111
|
+
except Exception as e:
|
|
112
|
+
warning(f"Failed to configure with certificate {cert_path}: {e}")
|
|
113
|
+
warning("You may run into errors when using the CLI.")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _validate_certificate(cert_path: Path) -> bool:
|
|
117
|
+
"""
|
|
118
|
+
Validate that a certificate file is readable and appears to be a valid certificate.
|
|
119
|
+
|
|
120
|
+
Parameters
|
|
121
|
+
----------
|
|
122
|
+
cert_path : Path
|
|
123
|
+
Path to the certificate file
|
|
124
|
+
|
|
125
|
+
Returns
|
|
126
|
+
-------
|
|
127
|
+
bool
|
|
128
|
+
True if the certificate appears valid, False otherwise
|
|
129
|
+
"""
|
|
130
|
+
try:
|
|
131
|
+
# Check if the file exists and is readable
|
|
132
|
+
if not cert_path.exists() or not cert_path.is_file():
|
|
133
|
+
return False
|
|
134
|
+
|
|
135
|
+
# Try to read the certificate content
|
|
136
|
+
with open(cert_path, "rb") as f:
|
|
137
|
+
cert_data = f.read()
|
|
138
|
+
|
|
139
|
+
# Basic validation: check if it looks like a PEM certificate
|
|
140
|
+
if b"-----BEGIN CERTIFICATE-----" not in cert_data:
|
|
141
|
+
return False
|
|
142
|
+
|
|
143
|
+
# Try to parse the certificate with Python's ssl module
|
|
144
|
+
if _validate_certificate_with_ssl(cert_data):
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
# If we can't parse it with ssl module, at least check it has the right
|
|
148
|
+
# structure
|
|
149
|
+
return (
|
|
150
|
+
b"-----BEGIN CERTIFICATE-----" in cert_data
|
|
151
|
+
and b"-----END CERTIFICATE-----" in cert_data
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
except Exception as e:
|
|
155
|
+
warning(f"Certificate validation failed: {e}")
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _validate_certificate_with_ssl(cert_data: bytes) -> bool:
|
|
160
|
+
"""
|
|
161
|
+
Validate a certificate with the ssl module.
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
cert_data : bytes
|
|
166
|
+
The certificate data
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
bool
|
|
171
|
+
True if the certificate appears valid, False otherwise
|
|
172
|
+
"""
|
|
173
|
+
try:
|
|
174
|
+
# Extract the base64 part between BEGIN and END
|
|
175
|
+
start_text = b"-----BEGIN CERTIFICATE-----"
|
|
176
|
+
len_start_text = len(start_text)
|
|
177
|
+
start = cert_data.find(start_text)
|
|
178
|
+
end = cert_data.find(b"-----END CERTIFICATE-----")
|
|
179
|
+
if start != -1 and end != -1:
|
|
180
|
+
cert_b64 = (
|
|
181
|
+
cert_data[start + len_start_text : end]
|
|
182
|
+
.replace(b"\n", b"")
|
|
183
|
+
.replace(b"\r", b"")
|
|
184
|
+
)
|
|
185
|
+
cert_der = base64.b64decode(cert_b64)
|
|
186
|
+
ssl.DER_cert_to_PEM_cert(cert_der)
|
|
187
|
+
return True
|
|
188
|
+
except Exception:
|
|
189
|
+
pass
|
|
190
|
+
return False
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def load_kubernetes_config_with_fallback() -> bool:
|
|
194
|
+
"""
|
|
195
|
+
Load Kubernetes configuration with fallback for development environments.
|
|
196
|
+
|
|
197
|
+
This function tries to load the Kubernetes configuration and handles
|
|
198
|
+
common issues like SSL certificate problems in development environments.
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
bool
|
|
203
|
+
True if configuration was loaded successfully, False otherwise
|
|
204
|
+
"""
|
|
205
|
+
# Try to load in-cluster config first (for when running inside Kubernetes)
|
|
206
|
+
try:
|
|
207
|
+
config.load_incluster_config()
|
|
208
|
+
return True
|
|
209
|
+
except ConfigException:
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
# Fallback to kubeconfig
|
|
213
|
+
try:
|
|
214
|
+
# Load kubeconfig into default config, then adjust CA if MicroK8s
|
|
215
|
+
config.load_kube_config()
|
|
216
|
+
cfg = client.Configuration.get_default_copy()
|
|
217
|
+
configure_kubernetes_client_for_microk8s(cfg)
|
|
218
|
+
client.Configuration.set_default(cfg)
|
|
219
|
+
|
|
220
|
+
return True
|
|
221
|
+
except ConfigException as exc:
|
|
222
|
+
warning(f"Failed to load Kubernetes configuration: {exc}")
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def create_kubernetes_apis_with_ssl_handling() -> tuple[
|
|
227
|
+
client.CoreV1Api, client.BatchV1Api
|
|
228
|
+
]:
|
|
229
|
+
"""
|
|
230
|
+
Create Kubernetes API clients with SSL handling for development environments.
|
|
231
|
+
|
|
232
|
+
Returns
|
|
233
|
+
-------
|
|
234
|
+
tuple[client.CoreV1Api, client.BatchV1Api]
|
|
235
|
+
Tuple of CoreV1Api and BatchV1Api clients
|
|
236
|
+
"""
|
|
237
|
+
# Load configuration with fallback handling
|
|
238
|
+
if not load_kubernetes_config_with_fallback():
|
|
239
|
+
raise RuntimeError("Failed to load Kubernetes configuration")
|
|
240
|
+
|
|
241
|
+
# Create API clients
|
|
242
|
+
core_api = client.CoreV1Api()
|
|
243
|
+
batch_api = client.BatchV1Api()
|
|
244
|
+
|
|
245
|
+
return core_api, batch_api
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def get_core_api_with_ssl_handling() -> client.CoreV1Api:
|
|
249
|
+
"""
|
|
250
|
+
Get the CoreV1Api client with SSL handling for development environments.
|
|
251
|
+
"""
|
|
252
|
+
core_api, _ = create_kubernetes_apis_with_ssl_handling()
|
|
253
|
+
return core_api
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def is_microk8s_context() -> bool:
|
|
257
|
+
"""
|
|
258
|
+
Check if the current Kubernetes context is MicroK8s.
|
|
259
|
+
|
|
260
|
+
Returns
|
|
261
|
+
-------
|
|
262
|
+
bool
|
|
263
|
+
True if using MicroK8s context, False otherwise
|
|
264
|
+
"""
|
|
265
|
+
try:
|
|
266
|
+
_, active_context = config.list_kube_config_contexts()
|
|
267
|
+
current_context = active_context.get("name", "") if active_context else ""
|
|
268
|
+
return "microk8s" in current_context.lower()
|
|
269
|
+
except Exception:
|
|
270
|
+
return False
|