vantage6 4.2.2__py3-none-any.whl → 4.3.0b3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of vantage6 might be problematic. Click here for more details.
- tests_cli/test_node_cli.py +1 -1
- tests_cli/test_server_cli.py +5 -5
- tests_cli/test_wizard.py +8 -9
- vantage6/cli/__build__ +1 -1
- vantage6/cli/_version.py +1 -1
- vantage6/cli/algostore/attach.py +32 -0
- vantage6/cli/algostore/new.py +55 -0
- vantage6/cli/algostore/start.py +102 -0
- vantage6/cli/algostore/stop.py +60 -0
- vantage6/cli/cli.py +20 -0
- vantage6/cli/common/decorator.py +92 -0
- vantage6/cli/common/start.py +232 -0
- vantage6/cli/configuration_manager.py +13 -23
- vantage6/cli/configuration_wizard.py +134 -60
- vantage6/cli/context/__init__.py +86 -0
- vantage6/cli/context/algorithm_store.py +130 -0
- vantage6/cli/context/base_server.py +89 -0
- vantage6/cli/{context.py → context/node.py} +12 -174
- vantage6/cli/context/server.py +127 -0
- vantage6/cli/dev/create.py +4 -3
- vantage6/cli/dev/remove.py +5 -3
- vantage6/cli/dev/start.py +2 -1
- vantage6/cli/dev/stop.py +2 -1
- vantage6/cli/globals.py +24 -0
- vantage6/cli/node/common/__init__.py +10 -4
- vantage6/cli/node/create_private_key.py +1 -1
- vantage6/cli/node/files.py +1 -1
- vantage6/cli/node/list.py +1 -1
- vantage6/cli/node/new.py +3 -2
- vantage6/cli/node/remove.py +1 -1
- vantage6/cli/node/set_api_key.py +1 -1
- vantage6/cli/node/start.py +7 -6
- vantage6/cli/rabbitmq/queue_manager.py +1 -1
- vantage6/cli/server/attach.py +3 -3
- vantage6/cli/server/common/__init__.py +5 -3
- vantage6/cli/server/files.py +1 -1
- vantage6/cli/server/import_.py +7 -3
- vantage6/cli/server/list.py +9 -5
- vantage6/cli/server/new.py +3 -2
- vantage6/cli/server/shell.py +3 -3
- vantage6/cli/server/start.py +26 -110
- vantage6/cli/server/stop.py +3 -4
- vantage6/cli/server/version.py +2 -2
- {vantage6-4.2.2.dist-info → vantage6-4.3.0b3.dist-info}/METADATA +3 -3
- vantage6-4.3.0b3.dist-info/RECORD +68 -0
- vantage6-4.2.2.dist-info/RECORD +0 -58
- {vantage6-4.2.2.dist-info → vantage6-4.3.0b3.dist-info}/WHEEL +0 -0
- {vantage6-4.2.2.dist-info → vantage6-4.3.0b3.dist-info}/entry_points.txt +0 -0
- {vantage6-4.2.2.dist-info → vantage6-4.3.0b3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from threading import Thread
|
|
3
|
+
import time
|
|
4
|
+
import docker
|
|
5
|
+
import enum
|
|
6
|
+
from docker.client import DockerClient
|
|
7
|
+
from docker.models.containers import Container
|
|
8
|
+
from colorama import Fore, Style
|
|
9
|
+
from sqlalchemy.engine.url import make_url
|
|
10
|
+
|
|
11
|
+
from vantage6.common import error, info, warning
|
|
12
|
+
from vantage6.common.context import AppContext
|
|
13
|
+
from vantage6.common.globals import InstanceType, APPNAME, DEFAULT_DOCKER_REGISTRY
|
|
14
|
+
from vantage6.common.docker.addons import check_docker_running, pull_if_newer
|
|
15
|
+
from vantage6.cli.context import AlgorithmStoreContext, ServerContext
|
|
16
|
+
from vantage6.cli.server.common import print_log_worker
|
|
17
|
+
from vantage6.cli.utils import check_config_name_allowed
|
|
18
|
+
from vantage6.cli.globals import ServerGlobals, AlgoStoreGlobals
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def check_for_start(ctx: AppContext, type_: InstanceType) -> DockerClient:
|
|
22
|
+
"""
|
|
23
|
+
Check if all requirements are met to start the instance.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
ctx : AppContext
|
|
28
|
+
The context object
|
|
29
|
+
type_ : InstanceType
|
|
30
|
+
The type of instance to check for
|
|
31
|
+
"""
|
|
32
|
+
# will print an error if not
|
|
33
|
+
check_docker_running()
|
|
34
|
+
|
|
35
|
+
info("Finding Docker daemon.")
|
|
36
|
+
docker_client = docker.from_env()
|
|
37
|
+
|
|
38
|
+
# check if name is allowed for docker volume, else exit
|
|
39
|
+
check_config_name_allowed(ctx.name)
|
|
40
|
+
|
|
41
|
+
# check that this server is not already running
|
|
42
|
+
running_servers = docker_client.containers.list(
|
|
43
|
+
filters={"label": f"{APPNAME}-type={type_}"}
|
|
44
|
+
)
|
|
45
|
+
for server in running_servers:
|
|
46
|
+
if server.name == f"{APPNAME}-{ctx.name}-{ctx.scope}-{type_}":
|
|
47
|
+
error(f"Server {Fore.RED}{ctx.name}{Style.RESET_ALL} " "is already running")
|
|
48
|
+
exit(1)
|
|
49
|
+
return docker_client
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_image(
|
|
53
|
+
image: str, ctx: AppContext, custom_image_key: str, default_image: str
|
|
54
|
+
) -> str:
|
|
55
|
+
"""
|
|
56
|
+
Get the image name for the given instance type.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
image : str | None
|
|
61
|
+
The image name to use if specified
|
|
62
|
+
ctx : AppContext
|
|
63
|
+
The context object
|
|
64
|
+
custom_image_key : str
|
|
65
|
+
The key to look for in the config file
|
|
66
|
+
default_image : str
|
|
67
|
+
The default image name
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
str
|
|
72
|
+
The image name to use
|
|
73
|
+
"""
|
|
74
|
+
# Determine image-name. First we check if the option --image has been used.
|
|
75
|
+
# Then we check if the image has been specified in the config file, and
|
|
76
|
+
# finally we use the default settings from the package.
|
|
77
|
+
if image is None:
|
|
78
|
+
custom_images: dict = ctx.config.get("images")
|
|
79
|
+
if custom_images:
|
|
80
|
+
image = custom_images.get(custom_image_key)
|
|
81
|
+
if not image:
|
|
82
|
+
image = f"{DEFAULT_DOCKER_REGISTRY}/{default_image}"
|
|
83
|
+
return image
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def pull_image(docker_client: DockerClient, image: str) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Pull the image if it is not already available.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
docker : DockerClient
|
|
93
|
+
The docker client
|
|
94
|
+
image : str
|
|
95
|
+
The image name
|
|
96
|
+
"""
|
|
97
|
+
info(f"Pulling latest image '{image}'.")
|
|
98
|
+
try:
|
|
99
|
+
pull_if_newer(docker_client.from_env(), image)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
warning(" ... Getting latest server image failed:")
|
|
102
|
+
warning(f" {e}")
|
|
103
|
+
else:
|
|
104
|
+
info(" ... success!")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def mount_config_file(ctx: AppContext, config_file: str) -> list[docker.types.Mount]:
|
|
108
|
+
"""
|
|
109
|
+
Mount the config file in the container.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
ctx : AppContext
|
|
114
|
+
The context object
|
|
115
|
+
config_file : str
|
|
116
|
+
The path to the config file
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
list[docker.types.Mount]
|
|
121
|
+
The mounts to use
|
|
122
|
+
"""
|
|
123
|
+
info("Creating mounts")
|
|
124
|
+
return [docker.types.Mount(config_file, str(ctx.config_file), type="bind")]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def mount_source(mount_src: str) -> docker.types.Mount:
|
|
128
|
+
"""
|
|
129
|
+
Mount the vantage6 source code in the container.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
mount_src : str
|
|
134
|
+
The path to the source code
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
docker.types.Mount | None
|
|
139
|
+
The mount to use
|
|
140
|
+
"""
|
|
141
|
+
if mount_src:
|
|
142
|
+
mount_src = os.path.abspath(mount_src)
|
|
143
|
+
return docker.types.Mount("/vantage6", mount_src, type="bind")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def mount_database(
|
|
147
|
+
ctx: ServerContext | AlgorithmStoreContext, type_: InstanceType
|
|
148
|
+
) -> tuple[docker.types.Mount, dict]:
|
|
149
|
+
"""
|
|
150
|
+
Mount database in the container if it is file-based (e.g. a SQLite DB).
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
ctx : AppContext
|
|
155
|
+
The context object
|
|
156
|
+
type_ : InstanceType
|
|
157
|
+
The type of instance to mount the database for
|
|
158
|
+
|
|
159
|
+
Returns
|
|
160
|
+
-------
|
|
161
|
+
docker.types.Mount | None
|
|
162
|
+
The mount to use
|
|
163
|
+
dict | None
|
|
164
|
+
The environment variables to use
|
|
165
|
+
"""
|
|
166
|
+
# FIXME: code duplication with cli_server_import()
|
|
167
|
+
# try to mount database
|
|
168
|
+
uri = ctx.config["uri"]
|
|
169
|
+
url = make_url(uri)
|
|
170
|
+
environment_vars = None
|
|
171
|
+
mount = None
|
|
172
|
+
|
|
173
|
+
# If host is None, we're dealing with a file-based DB, like SQLite
|
|
174
|
+
if url.host is None:
|
|
175
|
+
db_path = url.database
|
|
176
|
+
|
|
177
|
+
if not os.path.isabs(db_path):
|
|
178
|
+
# We're dealing with a relative path here -> make it absolute
|
|
179
|
+
db_path = ctx.data_dir / url.database
|
|
180
|
+
|
|
181
|
+
basename = os.path.basename(db_path)
|
|
182
|
+
dirname = os.path.dirname(db_path)
|
|
183
|
+
os.makedirs(dirname, exist_ok=True)
|
|
184
|
+
|
|
185
|
+
# we're mounting the entire folder that contains the database
|
|
186
|
+
mount = docker.types.Mount("/mnt/database/", dirname, type="bind")
|
|
187
|
+
|
|
188
|
+
if type_ == InstanceType.SERVER:
|
|
189
|
+
environment_vars = {
|
|
190
|
+
ServerGlobals.DB_URI_ENV_VAR.value: f"sqlite:////mnt/database/{basename}",
|
|
191
|
+
ServerGlobals.CONFIG_NAME_ENV_VAR.value: ctx.config_file_name,
|
|
192
|
+
}
|
|
193
|
+
elif type_ == InstanceType.ALGORITHM_STORE:
|
|
194
|
+
environment_vars = {
|
|
195
|
+
AlgoStoreGlobals.DB_URI_ENV_VAR.value: f"sqlite:////mnt/database/{basename}",
|
|
196
|
+
AlgoStoreGlobals.CONFIG_NAME_ENV_VAR.value: ctx.config_file_name,
|
|
197
|
+
}
|
|
198
|
+
else:
|
|
199
|
+
warning(
|
|
200
|
+
f"Database could not be transferred, make sure {url.host} "
|
|
201
|
+
"is reachable from the Docker container"
|
|
202
|
+
)
|
|
203
|
+
info("Consider using the docker-compose method to start a server")
|
|
204
|
+
|
|
205
|
+
return mount, environment_vars
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def attach_logs(container: Container, type_: InstanceType) -> None:
|
|
209
|
+
"""
|
|
210
|
+
Attach container logs to the console if specified.
|
|
211
|
+
|
|
212
|
+
Parameters
|
|
213
|
+
----------
|
|
214
|
+
container : Container
|
|
215
|
+
The container to attach the logs from
|
|
216
|
+
type_ : InstanceType
|
|
217
|
+
The type of instance to attach the logs for
|
|
218
|
+
"""
|
|
219
|
+
if isinstance(type_, enum.Enum):
|
|
220
|
+
type_ = type_.value
|
|
221
|
+
logs = container.attach(stream=True, logs=True, stdout=True)
|
|
222
|
+
Thread(target=print_log_worker, args=(logs,), daemon=True).start()
|
|
223
|
+
while True:
|
|
224
|
+
try:
|
|
225
|
+
time.sleep(1)
|
|
226
|
+
except KeyboardInterrupt:
|
|
227
|
+
info("Closing log file. Keyboard Interrupt.")
|
|
228
|
+
info(
|
|
229
|
+
"Note that your server is still running! Shut it down "
|
|
230
|
+
f"with {Fore.RED}v6 {type_} stop{Style.RESET_ALL}"
|
|
231
|
+
)
|
|
232
|
+
exit(0)
|
|
@@ -2,6 +2,17 @@ from schema import And, Or, Use, Optional
|
|
|
2
2
|
|
|
3
3
|
from vantage6.common.configuration_manager import Configuration, ConfigurationManager
|
|
4
4
|
|
|
5
|
+
LOGGING_VALIDATORS = {
|
|
6
|
+
"level": And(
|
|
7
|
+
Use(str), lambda lvl: lvl in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
|
|
8
|
+
),
|
|
9
|
+
"use_console": Use(bool),
|
|
10
|
+
"backup_count": And(Use(int), lambda n: n > 0),
|
|
11
|
+
"max_size": And(Use(int), lambda b: b > 16),
|
|
12
|
+
"format": Use(str),
|
|
13
|
+
"datefmt": Use(str),
|
|
14
|
+
}
|
|
15
|
+
|
|
5
16
|
|
|
6
17
|
class ServerConfiguration(Configuration):
|
|
7
18
|
"""
|
|
@@ -16,18 +27,7 @@ class ServerConfiguration(Configuration):
|
|
|
16
27
|
"api_path": Use(str),
|
|
17
28
|
"uri": Use(str),
|
|
18
29
|
"allow_drop_all": Use(bool),
|
|
19
|
-
"logging": {
|
|
20
|
-
"level": And(
|
|
21
|
-
Use(str),
|
|
22
|
-
lambda lvl: lvl in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"),
|
|
23
|
-
),
|
|
24
|
-
"file": Use(str),
|
|
25
|
-
"use_console": Use(bool),
|
|
26
|
-
"backup_count": And(Use(int), lambda n: n > 0),
|
|
27
|
-
"max_size": And(Use(int), lambda b: b > 16),
|
|
28
|
-
"format": Use(str),
|
|
29
|
-
"datefmt": Use(str),
|
|
30
|
-
},
|
|
30
|
+
"logging": {**LOGGING_VALIDATORS, "file": Use(str)},
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
|
|
@@ -45,17 +45,7 @@ class NodeConfiguration(Configuration):
|
|
|
45
45
|
# TODO: remove `dict` validation from databases
|
|
46
46
|
"databases": Or([Use(dict)], dict, None),
|
|
47
47
|
"api_path": Use(str),
|
|
48
|
-
"logging":
|
|
49
|
-
"level": And(
|
|
50
|
-
Use(str),
|
|
51
|
-
lambda lvl: lvl in ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"),
|
|
52
|
-
),
|
|
53
|
-
"use_console": Use(bool),
|
|
54
|
-
"backup_count": And(Use(int), lambda n: n > 0),
|
|
55
|
-
"max_size": And(Use(int), lambda b: b > 16),
|
|
56
|
-
"format": Use(str),
|
|
57
|
-
"datefmt": Use(str),
|
|
58
|
-
},
|
|
48
|
+
"logging": LOGGING_VALIDATORS,
|
|
59
49
|
"encryption": {"enabled": bool, Optional("private_key"): Use(str)},
|
|
60
50
|
Optional("node_extra_env"): dict,
|
|
61
51
|
Optional("node_extra_mounts"): [str],
|
|
@@ -3,14 +3,16 @@ import questionary as q
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
|
|
5
5
|
from vantage6.common import generate_apikey
|
|
6
|
-
from vantage6.common.globals import DATABASE_TYPES
|
|
6
|
+
from vantage6.common.globals import DATABASE_TYPES, InstanceType
|
|
7
7
|
from vantage6.common.client.node_client import NodeClient
|
|
8
|
+
from vantage6.common.context import AppContext
|
|
8
9
|
from vantage6.common import error, warning
|
|
9
|
-
from vantage6.cli.context import
|
|
10
|
+
from vantage6.cli.context import select_context_class
|
|
10
11
|
from vantage6.cli.configuration_manager import (
|
|
11
12
|
NodeConfigurationManager,
|
|
12
13
|
ServerConfigurationManager,
|
|
13
14
|
)
|
|
15
|
+
from vantage6.cli.globals import AlgoStoreGlobals, ServerGlobals
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
|
|
@@ -139,29 +141,32 @@ def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
|
|
|
139
141
|
|
|
140
142
|
private_key = "" if not encryption else q.text("Path to private key file:").ask()
|
|
141
143
|
|
|
142
|
-
config["encryption"] = {
|
|
143
|
-
"enabled": encryption is True or encryption == "true",
|
|
144
|
-
"private_key": private_key,
|
|
145
|
-
}
|
|
144
|
+
config["encryption"] = {"enabled": encryption == "true", "private_key": private_key}
|
|
146
145
|
|
|
147
146
|
return config
|
|
148
147
|
|
|
149
148
|
|
|
150
|
-
def
|
|
149
|
+
def _get_common_server_config(
|
|
150
|
+
instance_type: InstanceType, instance_name: str, include_api_path: bool = True
|
|
151
|
+
) -> dict:
|
|
151
152
|
"""
|
|
152
|
-
|
|
153
|
+
Part of the questionaire that is common to all server types (vantage6
|
|
154
|
+
server and algorithm store server).
|
|
153
155
|
|
|
154
156
|
Parameters
|
|
155
157
|
----------
|
|
158
|
+
instance_type : InstanceType
|
|
159
|
+
Type of server instance.
|
|
156
160
|
instance_name : str
|
|
157
|
-
Name of the
|
|
161
|
+
Name of the server instance.
|
|
162
|
+
include_api_path : bool
|
|
163
|
+
Whether to include the api path in the questionaire.
|
|
158
164
|
|
|
159
165
|
Returns
|
|
160
166
|
-------
|
|
161
167
|
dict
|
|
162
|
-
Dictionary with
|
|
168
|
+
Dictionary with new (partial) server configuration
|
|
163
169
|
"""
|
|
164
|
-
|
|
165
170
|
config = q.prompt(
|
|
166
171
|
[
|
|
167
172
|
{
|
|
@@ -174,38 +179,99 @@ def server_configuration_questionaire(instance_name: str) -> dict:
|
|
|
174
179
|
"type": "text",
|
|
175
180
|
"name": "port",
|
|
176
181
|
"message": "Enter port to which the server listens:",
|
|
177
|
-
"default":
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
"default": "/api",
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
"type": "text",
|
|
187
|
-
"name": "uri",
|
|
188
|
-
"message": "Database URI:",
|
|
189
|
-
"default": "sqlite:///default.sqlite",
|
|
190
|
-
},
|
|
191
|
-
{
|
|
192
|
-
"type": "select",
|
|
193
|
-
"name": "allow_drop_all",
|
|
194
|
-
"message": "Allowed to drop all tables: ",
|
|
195
|
-
"choices": ["True", "False"],
|
|
182
|
+
"default": (
|
|
183
|
+
# Note that .value is required in YAML to get proper str value
|
|
184
|
+
ServerGlobals.PORT.value
|
|
185
|
+
if instance_type == InstanceType.SERVER
|
|
186
|
+
else AlgoStoreGlobals.PORT.value
|
|
187
|
+
),
|
|
196
188
|
},
|
|
197
189
|
]
|
|
198
190
|
)
|
|
199
191
|
|
|
200
|
-
|
|
201
|
-
if
|
|
202
|
-
config
|
|
192
|
+
# TODO v5+ remove api_path. It complicates configuration
|
|
193
|
+
if include_api_path:
|
|
194
|
+
config.update(
|
|
195
|
+
q.prompt(
|
|
196
|
+
[
|
|
197
|
+
{
|
|
198
|
+
"type": "text",
|
|
199
|
+
"name": "api_path",
|
|
200
|
+
"message": "Path of the api:",
|
|
201
|
+
"default": "/api",
|
|
202
|
+
}
|
|
203
|
+
]
|
|
204
|
+
)
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
config.update(
|
|
208
|
+
q.prompt(
|
|
209
|
+
[
|
|
210
|
+
{
|
|
211
|
+
"type": "text",
|
|
212
|
+
"name": "uri",
|
|
213
|
+
"message": "Database URI:",
|
|
214
|
+
"default": "sqlite:///default.sqlite",
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"type": "select",
|
|
218
|
+
"name": "allow_drop_all",
|
|
219
|
+
"message": "Allowed to drop all tables: ",
|
|
220
|
+
"choices": ["True", "False"],
|
|
221
|
+
},
|
|
222
|
+
]
|
|
223
|
+
)
|
|
224
|
+
)
|
|
203
225
|
|
|
204
226
|
res = q.select(
|
|
205
227
|
"Which level of logging would you like?",
|
|
206
228
|
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "NOTSET"],
|
|
207
229
|
).ask()
|
|
208
230
|
|
|
231
|
+
config["logging"] = {
|
|
232
|
+
"level": res,
|
|
233
|
+
"file": f"{instance_name}.log",
|
|
234
|
+
"use_console": True,
|
|
235
|
+
"backup_count": 5,
|
|
236
|
+
"max_size": 1024,
|
|
237
|
+
"format": "%(asctime)s - %(name)-14s - %(levelname)-8s - %(message)s",
|
|
238
|
+
"datefmt": "%Y-%m-%d %H:%M:%S",
|
|
239
|
+
"loggers": [
|
|
240
|
+
{"name": "urllib3", "level": "warning"},
|
|
241
|
+
{"name": "socketIO-client", "level": "warning"},
|
|
242
|
+
{"name": "socketio.server", "level": "warning"},
|
|
243
|
+
{"name": "engineio.server", "level": "warning"},
|
|
244
|
+
{"name": "sqlalchemy.engine", "level": "warning"},
|
|
245
|
+
{"name": "requests_oauthlib.oauth2_session", "level": "warning"},
|
|
246
|
+
],
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return config
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def server_configuration_questionaire(instance_name: str) -> dict:
|
|
253
|
+
"""
|
|
254
|
+
Questionary to generate a config file for the server instance.
|
|
255
|
+
|
|
256
|
+
Parameters
|
|
257
|
+
----------
|
|
258
|
+
instance_name : str
|
|
259
|
+
Name of the server instance.
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
dict
|
|
264
|
+
Dictionary with the new server configuration
|
|
265
|
+
"""
|
|
266
|
+
|
|
267
|
+
config = _get_common_server_config(
|
|
268
|
+
InstanceType.SERVER, instance_name, include_api_path=True
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
constant_jwt_secret = q.confirm("Do you want a constant JWT secret?").ask()
|
|
272
|
+
if constant_jwt_secret:
|
|
273
|
+
config["jwt_secret_key"] = generate_apikey()
|
|
274
|
+
|
|
209
275
|
is_mfa = q.confirm("Do you want to enforce two-factor authentication?").ask()
|
|
210
276
|
if is_mfa:
|
|
211
277
|
config["two_factor_auth"] = is_mfa
|
|
@@ -260,35 +326,40 @@ def server_configuration_questionaire(instance_name: str) -> dict:
|
|
|
260
326
|
"start_with_server": run_rabbit_locally,
|
|
261
327
|
}
|
|
262
328
|
|
|
263
|
-
config
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
"datefmt": "%Y-%m-%d %H:%M:%S",
|
|
271
|
-
"loggers": [
|
|
272
|
-
{"name": "urllib3", "level": "warning"},
|
|
273
|
-
{"name": "socketIO-client", "level": "warning"},
|
|
274
|
-
{"name": "socketio.server", "level": "warning"},
|
|
275
|
-
{"name": "engineio.server", "level": "warning"},
|
|
276
|
-
{"name": "sqlalchemy.engine", "level": "warning"},
|
|
277
|
-
{"name": "requests_oauthlib.oauth2_session", "level": "warning"},
|
|
278
|
-
],
|
|
279
|
-
}
|
|
329
|
+
return config
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def algo_store_configuration_questionaire(instance_name: str) -> dict:
|
|
333
|
+
"""
|
|
334
|
+
Questionary to generate a config file for the algorithm store server
|
|
335
|
+
instance.
|
|
280
336
|
|
|
337
|
+
Parameters
|
|
338
|
+
----------
|
|
339
|
+
instance_name : str
|
|
340
|
+
Name of the server instance.
|
|
341
|
+
|
|
342
|
+
Returns
|
|
343
|
+
-------
|
|
344
|
+
dict
|
|
345
|
+
Dictionary with the new server configuration
|
|
346
|
+
"""
|
|
347
|
+
config = _get_common_server_config(
|
|
348
|
+
InstanceType.ALGORITHM_STORE, instance_name, include_api_path=False
|
|
349
|
+
)
|
|
281
350
|
return config
|
|
282
351
|
|
|
283
352
|
|
|
284
|
-
def configuration_wizard(
|
|
353
|
+
def configuration_wizard(
|
|
354
|
+
type_: InstanceType, instance_name: str, system_folders: bool
|
|
355
|
+
) -> Path:
|
|
285
356
|
"""
|
|
286
357
|
Create a configuration file for a node or server instance.
|
|
287
358
|
|
|
288
359
|
Parameters
|
|
289
360
|
----------
|
|
290
|
-
type_ :
|
|
291
|
-
Type of the instance
|
|
361
|
+
type_ : InstanceType
|
|
362
|
+
Type of the instance to create a configuration for
|
|
292
363
|
instance_name : str
|
|
293
364
|
Name of the instance
|
|
294
365
|
system_folders : bool
|
|
@@ -300,15 +371,18 @@ def configuration_wizard(type_: str, instance_name: str, system_folders: bool) -
|
|
|
300
371
|
Path to the configuration file
|
|
301
372
|
"""
|
|
302
373
|
# for defaults and where to save the config
|
|
303
|
-
dirs =
|
|
374
|
+
dirs = AppContext.instance_folders(type_, instance_name, system_folders)
|
|
304
375
|
|
|
305
376
|
# invoke questionaire to create configuration file
|
|
306
|
-
if type_ ==
|
|
377
|
+
if type_ == InstanceType.NODE:
|
|
307
378
|
conf_manager = NodeConfigurationManager
|
|
308
379
|
config = node_configuration_questionaire(dirs, instance_name)
|
|
309
|
-
|
|
380
|
+
elif type_ == InstanceType.SERVER:
|
|
310
381
|
conf_manager = ServerConfigurationManager
|
|
311
382
|
config = server_configuration_questionaire(instance_name)
|
|
383
|
+
else:
|
|
384
|
+
conf_manager = ServerConfigurationManager
|
|
385
|
+
config = algo_store_configuration_questionaire(instance_name)
|
|
312
386
|
|
|
313
387
|
# in the case of an environment we need to add it to the current
|
|
314
388
|
# configuration. In the case of application we can simply overwrite this
|
|
@@ -326,15 +400,15 @@ def configuration_wizard(type_: str, instance_name: str, system_folders: bool) -
|
|
|
326
400
|
return config_file
|
|
327
401
|
|
|
328
402
|
|
|
329
|
-
def select_configuration_questionaire(type_:
|
|
403
|
+
def select_configuration_questionaire(type_: InstanceType, system_folders: bool) -> str:
|
|
330
404
|
"""
|
|
331
405
|
Ask which configuration the user wants to use. It shows only configurations
|
|
332
406
|
that are in the default folder.
|
|
333
407
|
|
|
334
408
|
Parameters
|
|
335
409
|
----------
|
|
336
|
-
type_ :
|
|
337
|
-
Type of the instance
|
|
410
|
+
type_ : InstanceType
|
|
411
|
+
Type of the instance to create a configuration for
|
|
338
412
|
system_folders : bool
|
|
339
413
|
Whether to use the system folders or not
|
|
340
414
|
|
|
@@ -343,7 +417,7 @@ def select_configuration_questionaire(type_: str, system_folders: bool) -> str:
|
|
|
343
417
|
str
|
|
344
418
|
Name of the configuration
|
|
345
419
|
"""
|
|
346
|
-
context =
|
|
420
|
+
context = select_context_class(type_)
|
|
347
421
|
configs, _ = context.available_configurations(system_folders)
|
|
348
422
|
|
|
349
423
|
# each collection (file) can contain multiple configs. (e.g. test,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""
|
|
2
|
+
The context module in the CLI package contains the Context classes of instances
|
|
3
|
+
started from the CLI, such as nodes and servers. These contexts are related to
|
|
4
|
+
the host system and therefore part of the CLI package.
|
|
5
|
+
|
|
6
|
+
All classes are derived from the abstract AppContext class and provide the
|
|
7
|
+
vantage6 applications with naming conventions, standard file locations, and
|
|
8
|
+
more.
|
|
9
|
+
"""
|
|
10
|
+
from colorama import Fore, Style
|
|
11
|
+
|
|
12
|
+
from vantage6.common.globals import InstanceType
|
|
13
|
+
from vantage6.common import error
|
|
14
|
+
from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
|
|
15
|
+
from vantage6.cli.context.node import NodeContext
|
|
16
|
+
from vantage6.cli.context.server import ServerContext
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def select_context_class(
|
|
20
|
+
type_: InstanceType,
|
|
21
|
+
) -> ServerContext | NodeContext | AlgorithmStoreContext:
|
|
22
|
+
"""
|
|
23
|
+
Select the context class based on the type of instance.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
type_ : InstanceType
|
|
28
|
+
The type of instance for which the context should be inserted
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
ServerContext | NodeContext | AlgorithmStoreContext
|
|
33
|
+
Specialized subclass of AppContext for the given instance type
|
|
34
|
+
|
|
35
|
+
Raises
|
|
36
|
+
------
|
|
37
|
+
NotImplementedError
|
|
38
|
+
If the type_ is not implemented
|
|
39
|
+
"""
|
|
40
|
+
if type_ == InstanceType.SERVER:
|
|
41
|
+
return ServerContext
|
|
42
|
+
elif type_ == InstanceType.ALGORITHM_STORE:
|
|
43
|
+
return AlgorithmStoreContext
|
|
44
|
+
elif type_ == InstanceType.NODE:
|
|
45
|
+
return NodeContext
|
|
46
|
+
else:
|
|
47
|
+
raise NotImplementedError
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_context(
|
|
51
|
+
type_: InstanceType, name: str, system_folders: bool
|
|
52
|
+
) -> ServerContext | NodeContext | AlgorithmStoreContext:
|
|
53
|
+
"""
|
|
54
|
+
Load the server context from the configuration file.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
type_ : InstanceType
|
|
59
|
+
The type of instance to get the context for
|
|
60
|
+
name : str
|
|
61
|
+
Name of the instance
|
|
62
|
+
system_folders : bool
|
|
63
|
+
Wether to use system folders or if False, the user folders
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
AppContext
|
|
68
|
+
Specialized subclass context of AppContext for the given instance type
|
|
69
|
+
"""
|
|
70
|
+
ctx_class = select_context_class(type_)
|
|
71
|
+
if not ctx_class.config_exists(name, system_folders):
|
|
72
|
+
scope = "system" if system_folders else "user"
|
|
73
|
+
error(
|
|
74
|
+
f"Configuration {Fore.RED}{name}{Style.RESET_ALL} does not "
|
|
75
|
+
f"exist in the {Fore.RED}{scope}{Style.RESET_ALL} folders!"
|
|
76
|
+
)
|
|
77
|
+
exit(1)
|
|
78
|
+
|
|
79
|
+
# We do not want to log this here, we do this in the container and not on
|
|
80
|
+
# the host. We only want CLI logging here.
|
|
81
|
+
ctx_class.LOGGING_ENABLED = False
|
|
82
|
+
|
|
83
|
+
# create server context, and initialize db
|
|
84
|
+
ctx = ctx_class(name, system_folders=system_folders)
|
|
85
|
+
|
|
86
|
+
return ctx
|