vantage6 4.2.1__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_example.py +0 -1
- tests_cli/test_node_cli.py +74 -79
- tests_cli/test_server_cli.py +22 -22
- tests_cli/test_wizard.py +41 -29
- vantage6/cli/__build__ +1 -1
- vantage6/cli/_version.py +11 -8
- vantage6/cli/algorithm/create.py +14 -14
- vantage6/cli/algorithm/update.py +9 -6
- 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 +32 -12
- vantage6/cli/common/decorator.py +92 -0
- vantage6/cli/common/start.py +232 -0
- vantage6/cli/configuration_manager.py +22 -32
- vantage6/cli/configuration_wizard.py +255 -193
- 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/node.py +254 -0
- vantage6/cli/context/server.py +127 -0
- vantage6/cli/dev/create.py +180 -113
- vantage6/cli/dev/remove.py +20 -19
- vantage6/cli/dev/start.py +10 -10
- vantage6/cli/dev/stop.py +7 -5
- vantage6/cli/globals.py +24 -0
- vantage6/cli/node/attach.py +21 -10
- vantage6/cli/node/clean.py +4 -2
- vantage6/cli/node/common/__init__.py +15 -11
- vantage6/cli/node/create_private_key.py +58 -27
- vantage6/cli/node/files.py +14 -6
- vantage6/cli/node/list.py +18 -24
- vantage6/cli/node/new.py +21 -12
- vantage6/cli/node/remove.py +31 -22
- vantage6/cli/node/set_api_key.py +18 -12
- vantage6/cli/node/start.py +38 -12
- vantage6/cli/node/stop.py +32 -18
- vantage6/cli/node/version.py +23 -13
- vantage6/cli/rabbitmq/__init__.py +9 -9
- vantage6/cli/rabbitmq/definitions.py +24 -28
- vantage6/cli/rabbitmq/queue_manager.py +37 -40
- vantage6/cli/server/attach.py +16 -11
- vantage6/cli/server/common/__init__.py +37 -25
- vantage6/cli/server/files.py +1 -1
- vantage6/cli/server/import_.py +45 -37
- vantage6/cli/server/list.py +22 -23
- vantage6/cli/server/new.py +20 -14
- vantage6/cli/server/remove.py +5 -4
- vantage6/cli/server/shell.py +17 -6
- vantage6/cli/server/start.py +118 -174
- vantage6/cli/server/stop.py +31 -23
- vantage6/cli/server/version.py +16 -13
- vantage6/cli/test/common/diagnostic_runner.py +30 -34
- vantage6/cli/test/feature_tester.py +51 -25
- vantage6/cli/test/integration_test.py +69 -29
- vantage6/cli/utils.py +6 -5
- {vantage6-4.2.1.dist-info → vantage6-4.3.0b3.dist-info}/METADATA +5 -3
- vantage6-4.3.0b3.dist-info/RECORD +68 -0
- vantage6/cli/context.py +0 -416
- vantage6-4.2.1.dist-info/RECORD +0 -58
- {vantage6-4.2.1.dist-info → vantage6-4.3.0b3.dist-info}/WHEEL +0 -0
- {vantage6-4.2.1.dist-info → vantage6-4.3.0b3.dist-info}/entry_points.txt +0 -0
- {vantage6-4.2.1.dist-info → vantage6-4.3.0b3.dist-info}/top_level.txt +0 -0
vantage6/cli/algorithm/update.py
CHANGED
|
@@ -9,8 +9,14 @@ from vantage6.cli.utils import info
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
@click.command()
|
|
12
|
-
@click.option(
|
|
13
|
-
|
|
12
|
+
@click.option(
|
|
13
|
+
"-d",
|
|
14
|
+
"--dir",
|
|
15
|
+
"directory",
|
|
16
|
+
default=None,
|
|
17
|
+
type=str,
|
|
18
|
+
help="Directory to put the algorithm into",
|
|
19
|
+
)
|
|
14
20
|
def cli_algorithm_update(directory: str) -> dict:
|
|
15
21
|
"""Update an algorithm template
|
|
16
22
|
|
|
@@ -20,10 +26,7 @@ def cli_algorithm_update(directory: str) -> dict:
|
|
|
20
26
|
"""
|
|
21
27
|
if not directory:
|
|
22
28
|
default_dir = str(Path(os.getcwd()))
|
|
23
|
-
directory = q.text(
|
|
24
|
-
"Algorithm directory:",
|
|
25
|
-
default=default_dir
|
|
26
|
-
).ask()
|
|
29
|
+
directory = q.text("Algorithm directory:", default=default_dir).ask()
|
|
27
30
|
|
|
28
31
|
run_update(directory, overwrite=True)
|
|
29
32
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import docker
|
|
3
|
+
|
|
4
|
+
from colorama import Fore, Style
|
|
5
|
+
|
|
6
|
+
from vantage6.common import error
|
|
7
|
+
from vantage6.common.docker.addons import check_docker_running
|
|
8
|
+
from vantage6.common.globals import APPNAME, InstanceType
|
|
9
|
+
from vantage6.cli.common.decorator import insert_context
|
|
10
|
+
from vantage6.cli.common.start import attach_logs
|
|
11
|
+
from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command()
|
|
15
|
+
@insert_context(InstanceType.ALGORITHM_STORE)
|
|
16
|
+
def cli_algo_store_attach(ctx: AlgorithmStoreContext) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Show the server logs in the current console.
|
|
19
|
+
"""
|
|
20
|
+
check_docker_running()
|
|
21
|
+
client = docker.from_env()
|
|
22
|
+
|
|
23
|
+
running_servers = client.containers.list(
|
|
24
|
+
filters={"label": f"{APPNAME}-type={InstanceType.ALGORITHM_STORE}"}
|
|
25
|
+
)
|
|
26
|
+
running_server_names = [container.name for container in running_servers]
|
|
27
|
+
|
|
28
|
+
if ctx.docker_container_name in running_server_names:
|
|
29
|
+
container = client.containers.get(ctx.docker_container_name)
|
|
30
|
+
attach_logs(container, InstanceType.ALGORITHM_STORE)
|
|
31
|
+
else:
|
|
32
|
+
error(f"{Fore.RED}{ctx.name}{Style.RESET_ALL} is not running!")
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from colorama import Fore, Style
|
|
3
|
+
|
|
4
|
+
from vantage6.common import info, error, check_config_writeable
|
|
5
|
+
from vantage6.common.globals import InstanceType
|
|
6
|
+
from vantage6.cli.globals import DEFAULT_SERVER_SYSTEM_FOLDERS
|
|
7
|
+
from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
|
|
8
|
+
from vantage6.cli.configuration_wizard import configuration_wizard
|
|
9
|
+
from vantage6.cli.utils import check_config_name_allowed, prompt_config_name
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command()
|
|
13
|
+
@click.option(
|
|
14
|
+
"-n", "--name", default=None, help="name of the configuration you want to use."
|
|
15
|
+
)
|
|
16
|
+
@click.option("--system", "system_folders", flag_value=True)
|
|
17
|
+
@click.option(
|
|
18
|
+
"--user", "system_folders", flag_value=False, default=DEFAULT_SERVER_SYSTEM_FOLDERS
|
|
19
|
+
)
|
|
20
|
+
def cli_algo_store_new(name: str, system_folders: bool) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Create a new server configuration.
|
|
23
|
+
"""
|
|
24
|
+
name = prompt_config_name(name)
|
|
25
|
+
|
|
26
|
+
# check if name is allowed for docker volume, else exit
|
|
27
|
+
check_config_name_allowed(name)
|
|
28
|
+
|
|
29
|
+
# check that this config does not exist
|
|
30
|
+
try:
|
|
31
|
+
if AlgorithmStoreContext.config_exists(name, system_folders):
|
|
32
|
+
error(f"Configuration {Fore.RED}{name}{Style.RESET_ALL} already " "exists!")
|
|
33
|
+
exit(1)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
error(e)
|
|
36
|
+
exit(1)
|
|
37
|
+
|
|
38
|
+
# Check that we can write in this folder
|
|
39
|
+
if not check_config_writeable(system_folders):
|
|
40
|
+
error("Your user does not have write access to all folders. Exiting")
|
|
41
|
+
info(
|
|
42
|
+
f"Create a new server using '{Fore.GREEN}v6 algorithm-store new "
|
|
43
|
+
f"--user{Style.RESET_ALL}' instead!"
|
|
44
|
+
)
|
|
45
|
+
exit(1)
|
|
46
|
+
|
|
47
|
+
# create config in ctx location
|
|
48
|
+
cfg_file = configuration_wizard(InstanceType.ALGORITHM_STORE, name, system_folders)
|
|
49
|
+
info(f"New configuration created: {Fore.GREEN}{cfg_file}{Style.RESET_ALL}")
|
|
50
|
+
|
|
51
|
+
flag = "" if system_folders else "--user"
|
|
52
|
+
info(
|
|
53
|
+
f"You can start the algorithm store by running {Fore.GREEN}v6 "
|
|
54
|
+
f"algorithm-store start {flag}{Style.RESET_ALL}"
|
|
55
|
+
)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from vantage6.common import info
|
|
4
|
+
from vantage6.common.globals import APPNAME, DEFAULT_ALGO_STORE_IMAGE, InstanceType
|
|
5
|
+
from vantage6.cli.common.start import (
|
|
6
|
+
attach_logs,
|
|
7
|
+
check_for_start,
|
|
8
|
+
get_image,
|
|
9
|
+
mount_config_file,
|
|
10
|
+
mount_database,
|
|
11
|
+
mount_source,
|
|
12
|
+
pull_image,
|
|
13
|
+
)
|
|
14
|
+
from vantage6.cli.globals import AlgoStoreGlobals
|
|
15
|
+
from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
|
|
16
|
+
from vantage6.cli.common.decorator import insert_context
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.command()
|
|
20
|
+
@click.option("--ip", default=None, help="IP address to listen on")
|
|
21
|
+
@click.option("-p", "--port", default=None, type=int, help="Port to listen on")
|
|
22
|
+
@click.option("-i", "--image", default=None, help="Algorithm store Docker image to use")
|
|
23
|
+
@click.option(
|
|
24
|
+
"--keep/--auto-remove",
|
|
25
|
+
default=False,
|
|
26
|
+
help="Keep image after algorithm store has been stopped. Useful " "for debugging",
|
|
27
|
+
)
|
|
28
|
+
@click.option(
|
|
29
|
+
"--mount-src",
|
|
30
|
+
default="",
|
|
31
|
+
help="Override vantage6 source code in container with the source"
|
|
32
|
+
" code in this path",
|
|
33
|
+
)
|
|
34
|
+
@click.option(
|
|
35
|
+
"--attach/--detach",
|
|
36
|
+
default=False,
|
|
37
|
+
help="Print server logs to the console after start",
|
|
38
|
+
)
|
|
39
|
+
@insert_context(InstanceType.ALGORITHM_STORE)
|
|
40
|
+
def cli_algo_store_start(
|
|
41
|
+
ctx: AlgorithmStoreContext,
|
|
42
|
+
ip: str,
|
|
43
|
+
port: int,
|
|
44
|
+
image: str,
|
|
45
|
+
keep: bool,
|
|
46
|
+
mount_src: str,
|
|
47
|
+
attach: bool,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Start the algorithm store server.
|
|
51
|
+
"""
|
|
52
|
+
info("Starting algorithm store...")
|
|
53
|
+
docker_client = check_for_start(ctx, InstanceType.ALGORITHM_STORE)
|
|
54
|
+
|
|
55
|
+
image = get_image(image, ctx, "algorithm-store", DEFAULT_ALGO_STORE_IMAGE)
|
|
56
|
+
|
|
57
|
+
pull_image(docker_client, image)
|
|
58
|
+
|
|
59
|
+
config_file = "/mnt/config.yaml"
|
|
60
|
+
mounts = mount_config_file(ctx, config_file)
|
|
61
|
+
|
|
62
|
+
src_mount = mount_source(mount_src)
|
|
63
|
+
if src_mount:
|
|
64
|
+
mounts.append(src_mount)
|
|
65
|
+
|
|
66
|
+
mount, environment_vars = mount_database(ctx, InstanceType.ALGORITHM_STORE)
|
|
67
|
+
if mount:
|
|
68
|
+
mounts.append(mount)
|
|
69
|
+
|
|
70
|
+
# The `ip` and `port` refer here to the ip and port within the container.
|
|
71
|
+
# So we do not really care that is it listening on all interfaces.
|
|
72
|
+
internal_port = 5000
|
|
73
|
+
cmd = (
|
|
74
|
+
f"uwsgi --http :{internal_port} --gevent 1000 --http-websockets "
|
|
75
|
+
"--master --callable app --disable-logging "
|
|
76
|
+
"--wsgi-file /vantage6/vantage6-algorithm-store/vantage6/algorithm"
|
|
77
|
+
f"/store/wsgi.py --pyargv {config_file}"
|
|
78
|
+
)
|
|
79
|
+
info(cmd)
|
|
80
|
+
|
|
81
|
+
info("Run Docker container")
|
|
82
|
+
port_ = str(port or ctx.config["port"] or AlgoStoreGlobals.PORT)
|
|
83
|
+
container = docker_client.containers.run(
|
|
84
|
+
image,
|
|
85
|
+
command=cmd,
|
|
86
|
+
mounts=mounts,
|
|
87
|
+
detach=True,
|
|
88
|
+
labels={
|
|
89
|
+
f"{APPNAME}-type": InstanceType.ALGORITHM_STORE,
|
|
90
|
+
"name": ctx.config_file_name,
|
|
91
|
+
},
|
|
92
|
+
environment=environment_vars,
|
|
93
|
+
ports={f"{internal_port}/tcp": (ip, port_)},
|
|
94
|
+
name=ctx.docker_container_name,
|
|
95
|
+
auto_remove=not keep,
|
|
96
|
+
tty=True,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
info(f"Success! container id = {container.id}")
|
|
100
|
+
|
|
101
|
+
if attach:
|
|
102
|
+
attach_logs(container, InstanceType.ALGORITHM_STORE)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import docker
|
|
3
|
+
from colorama import Fore, Style
|
|
4
|
+
|
|
5
|
+
from vantage6.common import info, warning, error
|
|
6
|
+
from vantage6.common.docker.addons import (
|
|
7
|
+
check_docker_running,
|
|
8
|
+
remove_container_if_exists,
|
|
9
|
+
)
|
|
10
|
+
from vantage6.common.globals import APPNAME, InstanceType
|
|
11
|
+
from vantage6.cli.common.decorator import insert_context
|
|
12
|
+
from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command()
|
|
16
|
+
@insert_context(InstanceType.ALGORITHM_STORE)
|
|
17
|
+
@click.option("--all", "all_servers", flag_value=True, help="Stop all servers")
|
|
18
|
+
def cli_algo_store_stop(ctx: AlgorithmStoreContext, all_servers: bool):
|
|
19
|
+
"""
|
|
20
|
+
Stop one or all running server(s).
|
|
21
|
+
"""
|
|
22
|
+
check_docker_running()
|
|
23
|
+
client = docker.from_env()
|
|
24
|
+
|
|
25
|
+
running_servers = client.containers.list(
|
|
26
|
+
filters={"label": f"{APPNAME}-type={InstanceType.ALGORITHM_STORE}"}
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if not running_servers:
|
|
30
|
+
warning("No servers are currently running.")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
running_server_names = [server.name for server in running_servers]
|
|
34
|
+
|
|
35
|
+
if all_servers:
|
|
36
|
+
for container_name in running_server_names:
|
|
37
|
+
_stop_algorithm_store(client, container_name)
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
container_name = ctx.docker_container_name
|
|
41
|
+
if container_name not in running_server_names:
|
|
42
|
+
error(f"{Fore.RED}{ctx.name}{Style.RESET_ALL} is not running!")
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
_stop_algorithm_store(client, container_name)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _stop_algorithm_store(client, container_name) -> None:
|
|
49
|
+
"""
|
|
50
|
+
Stop the algorithm store server.
|
|
51
|
+
|
|
52
|
+
Parameters
|
|
53
|
+
----------
|
|
54
|
+
client : DockerClient
|
|
55
|
+
The docker client
|
|
56
|
+
container_name : str
|
|
57
|
+
The name of the container to stop
|
|
58
|
+
"""
|
|
59
|
+
remove_container_if_exists(client, name=container_name)
|
|
60
|
+
info(f"Stopped the {Fore.GREEN}{container_name}{Style.RESET_ALL} server.")
|
vantage6/cli/cli.py
CHANGED
|
@@ -29,10 +29,14 @@ from vantage6.cli.algorithm.create import cli_algorithm_create
|
|
|
29
29
|
from vantage6.cli.algorithm.update import cli_algorithm_update
|
|
30
30
|
from vantage6.cli.test.feature_tester import cli_test_features
|
|
31
31
|
from vantage6.cli.test.integration_test import cli_test_integration
|
|
32
|
+
from vantage6.cli.algostore.attach import cli_algo_store_attach
|
|
33
|
+
from vantage6.cli.algostore.new import cli_algo_store_new
|
|
34
|
+
from vantage6.cli.algostore.start import cli_algo_store_start
|
|
35
|
+
from vantage6.cli.algostore.stop import cli_algo_store_stop
|
|
32
36
|
|
|
33
37
|
|
|
34
38
|
# Define the server group
|
|
35
|
-
@click.group(name=
|
|
39
|
+
@click.group(name="server")
|
|
36
40
|
def cli_server() -> None:
|
|
37
41
|
"""
|
|
38
42
|
Manage your vantage6 server instances.
|
|
@@ -40,16 +44,16 @@ def cli_server() -> None:
|
|
|
40
44
|
|
|
41
45
|
|
|
42
46
|
# Define the commands for the server group
|
|
43
|
-
cli_server.add_command(cli_server_attach, name=
|
|
44
|
-
cli_server.add_command(cli_server_files, name=
|
|
45
|
-
cli_server.add_command(cli_server_import, name=
|
|
46
|
-
cli_server.add_command(cli_server_configuration_list, name=
|
|
47
|
-
cli_server.add_command(cli_server_new, name=
|
|
48
|
-
cli_server.add_command(cli_server_remove, name=
|
|
49
|
-
cli_server.add_command(cli_server_shell, name=
|
|
50
|
-
cli_server.add_command(cli_server_start, name=
|
|
51
|
-
cli_server.add_command(cli_server_stop, name=
|
|
52
|
-
cli_server.add_command(cli_server_version, name=
|
|
47
|
+
cli_server.add_command(cli_server_attach, name="attach")
|
|
48
|
+
cli_server.add_command(cli_server_files, name="files")
|
|
49
|
+
cli_server.add_command(cli_server_import, name="import")
|
|
50
|
+
cli_server.add_command(cli_server_configuration_list, name="list")
|
|
51
|
+
cli_server.add_command(cli_server_new, name="new")
|
|
52
|
+
cli_server.add_command(cli_server_remove, name="remove")
|
|
53
|
+
cli_server.add_command(cli_server_shell, name="shell")
|
|
54
|
+
cli_server.add_command(cli_server_start, name="start")
|
|
55
|
+
cli_server.add_command(cli_server_stop, name="stop")
|
|
56
|
+
cli_server.add_command(cli_server_version, name="version")
|
|
53
57
|
|
|
54
58
|
|
|
55
59
|
# Define the node group
|
|
@@ -117,8 +121,23 @@ cli_test.add_command(cli_test_features, name="feature-test")
|
|
|
117
121
|
cli_test.add_command(cli_test_integration, name="integration-test")
|
|
118
122
|
|
|
119
123
|
|
|
124
|
+
# Define the algorithm-store group
|
|
125
|
+
@click.group(name="algorithm-store")
|
|
126
|
+
def cli_algo_store() -> None:
|
|
127
|
+
"""
|
|
128
|
+
Manage your vantage6 algorithm store server instances.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
# Define the commands for the test group
|
|
133
|
+
cli_algo_store.add_command(cli_algo_store_attach, name="attach")
|
|
134
|
+
cli_algo_store.add_command(cli_algo_store_new, name="new")
|
|
135
|
+
cli_algo_store.add_command(cli_algo_store_start, name="start")
|
|
136
|
+
cli_algo_store.add_command(cli_algo_store_stop, name="stop")
|
|
137
|
+
|
|
138
|
+
|
|
120
139
|
# Define the overall group
|
|
121
|
-
@click.group(name=
|
|
140
|
+
@click.group(name="cli")
|
|
122
141
|
def cli_complete() -> None:
|
|
123
142
|
"""
|
|
124
143
|
The `v6` command line interface allows you to manage your vantage6
|
|
@@ -134,3 +153,4 @@ cli_complete.add_command(cli_server)
|
|
|
134
153
|
cli_complete.add_command(cli_dev)
|
|
135
154
|
cli_complete.add_command(cli_algorithm)
|
|
136
155
|
cli_complete.add_command(cli_test)
|
|
156
|
+
cli_complete.add_command(cli_algo_store)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
import click
|
|
3
|
+
|
|
4
|
+
from vantage6.common import error
|
|
5
|
+
from vantage6.common.globals import InstanceType
|
|
6
|
+
from vantage6.cli.configuration_wizard import select_configuration_questionaire
|
|
7
|
+
from vantage6.cli.globals import DEFAULT_SERVER_SYSTEM_FOLDERS
|
|
8
|
+
from vantage6.cli.context import select_context_class, get_context
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# TODO to make this decorator usable by nodes as well, we should make the
|
|
12
|
+
# default for --user/--system configurable
|
|
13
|
+
def insert_context(type_: InstanceType) -> callable:
|
|
14
|
+
"""
|
|
15
|
+
Supply the Click function with an additional context parameter. The context
|
|
16
|
+
is passed to the function as the first argument.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
type_ : InstanceType
|
|
21
|
+
The type of instance for which the context should be inserted
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
Callable
|
|
26
|
+
Click function with context
|
|
27
|
+
|
|
28
|
+
Examples
|
|
29
|
+
--------
|
|
30
|
+
>>> @insert_context(InstanceType.SERVER)
|
|
31
|
+
>>> def cli_server_start(ctx: ServerContext, *args, **kwargs) -> None:
|
|
32
|
+
>>> pass
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def protection_decorator(func: callable) -> callable:
|
|
36
|
+
@click.option("-n", "--name", default=None, help="Name of the configuration.")
|
|
37
|
+
@click.option(
|
|
38
|
+
"-c",
|
|
39
|
+
"--config",
|
|
40
|
+
default=None,
|
|
41
|
+
help="Absolute path to " "configuration-file; overrides --name",
|
|
42
|
+
)
|
|
43
|
+
@click.option(
|
|
44
|
+
"--system",
|
|
45
|
+
"system_folders",
|
|
46
|
+
flag_value=True,
|
|
47
|
+
help="Use system folders instead of user folders. This " "is the default",
|
|
48
|
+
)
|
|
49
|
+
@click.option(
|
|
50
|
+
"--user",
|
|
51
|
+
"system_folders",
|
|
52
|
+
flag_value=False,
|
|
53
|
+
default=DEFAULT_SERVER_SYSTEM_FOLDERS,
|
|
54
|
+
help="Use user folders instead of system folders",
|
|
55
|
+
)
|
|
56
|
+
@wraps(func)
|
|
57
|
+
def decorator(
|
|
58
|
+
name: str, config: str, system_folders: bool, *args, **kwargs
|
|
59
|
+
) -> callable:
|
|
60
|
+
"""
|
|
61
|
+
Decorator function that adds the context to the function.
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
Callable
|
|
66
|
+
Decorated function
|
|
67
|
+
"""
|
|
68
|
+
ctx_class = select_context_class(type_)
|
|
69
|
+
# path to configuration file always overrides name
|
|
70
|
+
if config:
|
|
71
|
+
ctx = ctx_class.from_external_config_file(config, system_folders)
|
|
72
|
+
elif "ctx" in kwargs:
|
|
73
|
+
# if ctx is already in kwargs (typically when one click command
|
|
74
|
+
# calls another internally), use that existing ctx
|
|
75
|
+
ctx = kwargs.pop("ctx")
|
|
76
|
+
else:
|
|
77
|
+
# in case no name, ctx or config file is supplied, ask the user
|
|
78
|
+
# to select an existing config by name
|
|
79
|
+
if not name:
|
|
80
|
+
try:
|
|
81
|
+
# select configuration if none supplied
|
|
82
|
+
name = select_configuration_questionaire(type_, system_folders)
|
|
83
|
+
except Exception:
|
|
84
|
+
error("No configurations could be found!")
|
|
85
|
+
exit(1)
|
|
86
|
+
|
|
87
|
+
ctx = get_context(type_, name, system_folders)
|
|
88
|
+
return func(ctx, *args, **kwargs)
|
|
89
|
+
|
|
90
|
+
return decorator
|
|
91
|
+
|
|
92
|
+
return protection_decorator
|
|
@@ -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)
|