vantage6 4.0.2__py3-none-any.whl → 4.1.0b0__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 +60 -55
- tests_cli/test_server_cli.py +30 -30
- vantage6/cli/_version.py +1 -1
- vantage6/cli/cli.py +102 -0
- vantage6/cli/{dev.py → dev/create.py} +18 -151
- vantage6/cli/dev/remove.py +63 -0
- vantage6/cli/dev/start.py +52 -0
- vantage6/cli/dev/stop.py +30 -0
- vantage6/cli/node/attach.py +58 -0
- vantage6/cli/node/clean.py +40 -0
- vantage6/cli/node/common/__init__.py +124 -0
- vantage6/cli/node/create_private_key.py +139 -0
- vantage6/cli/node/files.py +34 -0
- vantage6/cli/node/list.py +62 -0
- vantage6/cli/node/new.py +46 -0
- vantage6/cli/node/remove.py +103 -0
- vantage6/cli/node/set_api_key.py +45 -0
- vantage6/cli/node/start.py +311 -0
- vantage6/cli/node/stop.py +73 -0
- vantage6/cli/node/version.py +47 -0
- vantage6/cli/server/attach.py +54 -0
- vantage6/cli/server/common/__init__.py +146 -0
- vantage6/cli/server/files.py +16 -0
- vantage6/cli/server/import_.py +144 -0
- vantage6/cli/server/list.py +60 -0
- vantage6/cli/server/new.py +50 -0
- vantage6/cli/server/shell.py +42 -0
- vantage6/cli/server/start.py +302 -0
- vantage6/cli/server/stop.py +158 -0
- vantage6/cli/server/version.py +46 -0
- {vantage6-4.0.2.dist-info → vantage6-4.1.0b0.dist-info}/METADATA +7 -6
- vantage6-4.1.0b0.dist-info/RECORD +52 -0
- vantage6-4.1.0b0.dist-info/entry_points.txt +5 -0
- vantage6/cli/node.py +0 -1092
- vantage6/cli/server.py +0 -1033
- vantage6-4.0.2.dist-info/RECORD +0 -28
- vantage6-4.0.2.dist-info/entry_points.txt +0 -4
- {vantage6-4.0.2.dist-info → vantage6-4.1.0b0.dist-info}/WHEEL +0 -0
- {vantage6-4.0.2.dist-info → vantage6-4.1.0b0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from threading import Thread
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
import docker
|
|
6
|
+
from sqlalchemy.engine.url import make_url
|
|
7
|
+
|
|
8
|
+
from vantage6.common import info, warning
|
|
9
|
+
from vantage6.common.docker.addons import check_docker_running
|
|
10
|
+
from vantage6.common.globals import (
|
|
11
|
+
APPNAME,
|
|
12
|
+
DEFAULT_DOCKER_REGISTRY,
|
|
13
|
+
DEFAULT_SERVER_IMAGE,
|
|
14
|
+
)
|
|
15
|
+
from vantage6.cli.context import ServerContext
|
|
16
|
+
from vantage6.cli.utils import check_config_name_allowed
|
|
17
|
+
from vantage6.cli.server.common import click_insert_context, print_log_worker
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# TODO this method has a lot of duplicated code from `start`
|
|
21
|
+
@click.command()
|
|
22
|
+
@click.argument('file', type=click.Path(exists=True))
|
|
23
|
+
@click.option('--drop-all', is_flag=True, default=False,
|
|
24
|
+
help="Drop all existing data before importing")
|
|
25
|
+
@click.option('-i', '--image', default=None, help="Node Docker image to use")
|
|
26
|
+
@click.option('--mount-src', default='',
|
|
27
|
+
help="Override vantage6 source code in container with the source"
|
|
28
|
+
" code in this path")
|
|
29
|
+
@click.option('--keep/--auto-remove', default=False,
|
|
30
|
+
help="Keep image after finishing. Useful for debugging")
|
|
31
|
+
@click.option('--wait', default=False, help="Wait for the import to finish")
|
|
32
|
+
@click_insert_context
|
|
33
|
+
def cli_server_import(
|
|
34
|
+
ctx: ServerContext, file: str, drop_all: bool, image: str, mount_src: str,
|
|
35
|
+
keep: bool, wait: bool
|
|
36
|
+
) -> None:
|
|
37
|
+
"""
|
|
38
|
+
Import vantage6 resources into a server instance.
|
|
39
|
+
|
|
40
|
+
This allows you to create organizations, collaborations, users, tasks, etc
|
|
41
|
+
from a yaml file.
|
|
42
|
+
|
|
43
|
+
The FILE_ argument should be a path to a yaml file containing the vantage6
|
|
44
|
+
formatted data to import.
|
|
45
|
+
"""
|
|
46
|
+
# will print an error if not
|
|
47
|
+
check_docker_running()
|
|
48
|
+
|
|
49
|
+
info("Starting server...")
|
|
50
|
+
info("Finding Docker daemon.")
|
|
51
|
+
docker_client = docker.from_env()
|
|
52
|
+
|
|
53
|
+
# check if name is allowed for docker volume, else exit
|
|
54
|
+
check_config_name_allowed(ctx.name)
|
|
55
|
+
|
|
56
|
+
# pull latest Docker image
|
|
57
|
+
if image is None:
|
|
58
|
+
image = ctx.config.get(
|
|
59
|
+
"image",
|
|
60
|
+
f"{DEFAULT_DOCKER_REGISTRY}/{DEFAULT_SERVER_IMAGE}"
|
|
61
|
+
)
|
|
62
|
+
info(f"Pulling latest server image '{image}'.")
|
|
63
|
+
try:
|
|
64
|
+
docker_client.images.pull(image)
|
|
65
|
+
except Exception as e:
|
|
66
|
+
warning(' ... Getting latest node image failed:')
|
|
67
|
+
warning(f" {e}")
|
|
68
|
+
else:
|
|
69
|
+
info(" ... success!")
|
|
70
|
+
|
|
71
|
+
info("Creating mounts")
|
|
72
|
+
mounts = [
|
|
73
|
+
docker.types.Mount(
|
|
74
|
+
"/mnt/config.yaml", str(ctx.config_file), type="bind"
|
|
75
|
+
),
|
|
76
|
+
docker.types.Mount(
|
|
77
|
+
"/mnt/import.yaml", str(file), type="bind"
|
|
78
|
+
)
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
# FIXME: code duplication with cli_server_start()
|
|
82
|
+
# try to mount database
|
|
83
|
+
uri = ctx.config['uri']
|
|
84
|
+
url = make_url(uri)
|
|
85
|
+
environment_vars = None
|
|
86
|
+
|
|
87
|
+
if mount_src:
|
|
88
|
+
mount_src = os.path.abspath(mount_src)
|
|
89
|
+
mounts.append(docker.types.Mount("/vantage6", mount_src, type="bind"))
|
|
90
|
+
|
|
91
|
+
# If host is None, we're dealing with a file-based DB, like SQLite
|
|
92
|
+
if (url.host is None):
|
|
93
|
+
db_path = url.database
|
|
94
|
+
|
|
95
|
+
if not os.path.isabs(db_path):
|
|
96
|
+
# We're dealing with a relative path here -> make it absolute
|
|
97
|
+
db_path = ctx.data_dir / url.database
|
|
98
|
+
|
|
99
|
+
basename = os.path.basename(db_path)
|
|
100
|
+
dirname = os.path.dirname(db_path)
|
|
101
|
+
os.makedirs(dirname, exist_ok=True)
|
|
102
|
+
|
|
103
|
+
# we're mounting the entire folder that contains the database
|
|
104
|
+
mounts.append(docker.types.Mount(
|
|
105
|
+
"/mnt/database/", dirname, type="bind"
|
|
106
|
+
))
|
|
107
|
+
|
|
108
|
+
environment_vars = {
|
|
109
|
+
"VANTAGE6_DB_URI": f"sqlite:////mnt/database/{basename}"
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
else:
|
|
113
|
+
warning(f"Database could not be transferred, make sure {url.host} "
|
|
114
|
+
"is reachable from the Docker container")
|
|
115
|
+
info("Consider using the docker-compose method to start a server")
|
|
116
|
+
|
|
117
|
+
drop_all_ = "--drop-all" if drop_all else ""
|
|
118
|
+
cmd = (f'vserver-local import -c /mnt/config.yaml {drop_all_} '
|
|
119
|
+
'/mnt/import.yaml')
|
|
120
|
+
|
|
121
|
+
info(cmd)
|
|
122
|
+
|
|
123
|
+
info("Run Docker container")
|
|
124
|
+
container = docker_client.containers.run(
|
|
125
|
+
image,
|
|
126
|
+
command=cmd,
|
|
127
|
+
mounts=mounts,
|
|
128
|
+
detach=True,
|
|
129
|
+
labels={
|
|
130
|
+
f"{APPNAME}-type": "server",
|
|
131
|
+
"name": ctx.config_file_name
|
|
132
|
+
},
|
|
133
|
+
environment=environment_vars,
|
|
134
|
+
auto_remove=not keep,
|
|
135
|
+
tty=True
|
|
136
|
+
)
|
|
137
|
+
logs = container.logs(stream=True, stdout=True)
|
|
138
|
+
Thread(target=print_log_worker, args=(logs,), daemon=False).start()
|
|
139
|
+
|
|
140
|
+
info(f"Success! container id = {container.id}")
|
|
141
|
+
|
|
142
|
+
if wait:
|
|
143
|
+
container.wait()
|
|
144
|
+
info("Container finished!")
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import docker
|
|
3
|
+
|
|
4
|
+
from colorama import (Fore, Style)
|
|
5
|
+
|
|
6
|
+
from vantage6.common import warning
|
|
7
|
+
from vantage6.common.docker.addons import check_docker_running
|
|
8
|
+
from vantage6.common.globals import APPNAME
|
|
9
|
+
from vantage6.cli.context import ServerContext
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@click.command()
|
|
13
|
+
def cli_server_configuration_list() -> None:
|
|
14
|
+
"""
|
|
15
|
+
Print the available server configurations.
|
|
16
|
+
"""
|
|
17
|
+
check_docker_running()
|
|
18
|
+
client = docker.from_env()
|
|
19
|
+
|
|
20
|
+
running_server = client.containers.list(
|
|
21
|
+
filters={"label": f"{APPNAME}-type=server"})
|
|
22
|
+
running_node_names = []
|
|
23
|
+
for node in running_server:
|
|
24
|
+
running_node_names.append(node.name)
|
|
25
|
+
|
|
26
|
+
header = \
|
|
27
|
+
"\nName"+(21*" ") + \
|
|
28
|
+
"Status"+(10*" ") + \
|
|
29
|
+
"System/User"
|
|
30
|
+
|
|
31
|
+
click.echo(header)
|
|
32
|
+
click.echo("-"*len(header))
|
|
33
|
+
|
|
34
|
+
running = Fore.GREEN + "Running" + Style.RESET_ALL
|
|
35
|
+
stopped = Fore.RED + "Not running" + Style.RESET_ALL
|
|
36
|
+
|
|
37
|
+
# system folders
|
|
38
|
+
configs, f1 = ServerContext.available_configurations(system_folders=True)
|
|
39
|
+
for config in configs:
|
|
40
|
+
status = running if f"{APPNAME}-{config.name}-system-server" in \
|
|
41
|
+
running_node_names else stopped
|
|
42
|
+
click.echo(
|
|
43
|
+
f"{config.name:25}"
|
|
44
|
+
f"{status:25} System "
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# user folders
|
|
48
|
+
configs, f2 = ServerContext.available_configurations(system_folders=False)
|
|
49
|
+
for config in configs:
|
|
50
|
+
status = running if f"{APPNAME}-{config.name}-user-server" in \
|
|
51
|
+
running_node_names else stopped
|
|
52
|
+
click.echo(
|
|
53
|
+
f"{config.name:25}"
|
|
54
|
+
f"{status:25} User "
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
click.echo("-"*85)
|
|
58
|
+
if len(f1)+len(f2):
|
|
59
|
+
warning(
|
|
60
|
+
f"{Fore.RED}Failed imports: {len(f1)+len(f2)}{Style.RESET_ALL}")
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from colorama import (Fore, Style)
|
|
3
|
+
|
|
4
|
+
from vantage6.common import info, error, check_config_writeable
|
|
5
|
+
from vantage6.cli.globals import DEFAULT_SERVER_SYSTEM_FOLDERS
|
|
6
|
+
from vantage6.cli.context import ServerContext
|
|
7
|
+
from vantage6.cli.configuration_wizard import configuration_wizard
|
|
8
|
+
from vantage6.cli.utils import check_config_name_allowed, prompt_config_name
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.command()
|
|
12
|
+
@click.option('-n', '--name', default=None,
|
|
13
|
+
help="name of the configuration you want to use.")
|
|
14
|
+
@click.option('--system', 'system_folders', flag_value=True)
|
|
15
|
+
@click.option('--user', 'system_folders', flag_value=False,
|
|
16
|
+
default=DEFAULT_SERVER_SYSTEM_FOLDERS)
|
|
17
|
+
def cli_server_new(name: str, system_folders: bool) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Create a new server configuration.
|
|
20
|
+
"""
|
|
21
|
+
name = prompt_config_name(name)
|
|
22
|
+
|
|
23
|
+
# check if name is allowed for docker volume, else exit
|
|
24
|
+
check_config_name_allowed(name)
|
|
25
|
+
|
|
26
|
+
# check that this config does not exist
|
|
27
|
+
try:
|
|
28
|
+
if ServerContext.config_exists(name, system_folders):
|
|
29
|
+
error(f"Configuration {Fore.RED}{name}{Style.RESET_ALL} already "
|
|
30
|
+
"exists!")
|
|
31
|
+
exit(1)
|
|
32
|
+
except Exception as e:
|
|
33
|
+
error(e)
|
|
34
|
+
exit(1)
|
|
35
|
+
|
|
36
|
+
# Check that we can write in this folder
|
|
37
|
+
if not check_config_writeable(system_folders):
|
|
38
|
+
error("Your user does not have write access to all folders. Exiting")
|
|
39
|
+
info(f"Create a new server using '{Fore.GREEN}v6 server new "
|
|
40
|
+
f"--user{Style.RESET_ALL}' instead!")
|
|
41
|
+
exit(1)
|
|
42
|
+
|
|
43
|
+
# create config in ctx location
|
|
44
|
+
cfg_file = configuration_wizard("server", name, system_folders)
|
|
45
|
+
info(f"New configuration created: {Fore.GREEN}{cfg_file}{Style.RESET_ALL}")
|
|
46
|
+
|
|
47
|
+
# info(f"root user created.")
|
|
48
|
+
flag = "" if system_folders else "--user"
|
|
49
|
+
info(f"You can start the server by running {Fore.GREEN}v6 server start "
|
|
50
|
+
f"{flag}{Style.RESET_ALL}")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import docker
|
|
5
|
+
from colorama import (Fore, Style)
|
|
6
|
+
|
|
7
|
+
from vantage6.common import info, error, debug as debug_msg
|
|
8
|
+
from vantage6.common.docker.addons import check_docker_running
|
|
9
|
+
from vantage6.common.globals import APPNAME
|
|
10
|
+
from vantage6.cli.context import ServerContext
|
|
11
|
+
from vantage6.cli.server.common import click_insert_context
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command()
|
|
15
|
+
@click_insert_context
|
|
16
|
+
def cli_server_shell(ctx: ServerContext) -> None:
|
|
17
|
+
"""
|
|
18
|
+
Run an iPython shell within a running server. This can be used to modify
|
|
19
|
+
the database.
|
|
20
|
+
|
|
21
|
+
NOTE: using the shell is no longer recommended as there is no validation on
|
|
22
|
+
the changes that you make. It is better to use the Python client or a
|
|
23
|
+
graphical user interface instead.
|
|
24
|
+
"""
|
|
25
|
+
# will print an error if not
|
|
26
|
+
check_docker_running()
|
|
27
|
+
|
|
28
|
+
docker_client = docker.from_env()
|
|
29
|
+
|
|
30
|
+
running_servers = docker_client.containers.list(
|
|
31
|
+
filters={"label": f"{APPNAME}-type=server"})
|
|
32
|
+
|
|
33
|
+
if ctx.docker_container_name not in [s.name for s in running_servers]:
|
|
34
|
+
error(f"Server {Fore.RED}{ctx.name}{Style.RESET_ALL} is not running?")
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
subprocess.run(['docker', 'exec', '-it', ctx.docker_container_name,
|
|
39
|
+
'vserver-local', 'shell', '-c', '/mnt/config.yaml'])
|
|
40
|
+
except Exception as e:
|
|
41
|
+
info("Failed to start subprocess...")
|
|
42
|
+
debug_msg(e)
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from threading import Thread
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
import docker
|
|
7
|
+
from colorama import (Fore, Style)
|
|
8
|
+
from sqlalchemy.engine.url import make_url
|
|
9
|
+
from docker.client import DockerClient
|
|
10
|
+
|
|
11
|
+
from vantage6.common import info, warning, error
|
|
12
|
+
from vantage6.common.docker.addons import (
|
|
13
|
+
pull_if_newer, check_docker_running
|
|
14
|
+
)
|
|
15
|
+
from vantage6.common.docker.network_manager import NetworkManager
|
|
16
|
+
from vantage6.common.globals import (
|
|
17
|
+
APPNAME,
|
|
18
|
+
DEFAULT_DOCKER_REGISTRY,
|
|
19
|
+
DEFAULT_SERVER_IMAGE,
|
|
20
|
+
DEFAULT_UI_IMAGE
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
from vantage6.cli.globals import DEFAULT_UI_PORT
|
|
24
|
+
from vantage6.cli.context import ServerContext
|
|
25
|
+
from vantage6.cli.utils import check_config_name_allowed
|
|
26
|
+
from vantage6.cli.rabbitmq.queue_manager import RabbitMQManager
|
|
27
|
+
from vantage6.cli.server.common import (
|
|
28
|
+
click_insert_context, print_log_worker, stop_ui
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@click.command()
|
|
33
|
+
@click.option('--ip', default=None, help='IP address to listen on')
|
|
34
|
+
@click.option('-p', '--port', default=None, type=int, help='Port to listen on')
|
|
35
|
+
@click.option('-i', '--image', default=None, help="Server Docker image to use")
|
|
36
|
+
@click.option('--with-ui', 'start_ui', flag_value=True, default=False,
|
|
37
|
+
help="Start the graphical User Interface as well")
|
|
38
|
+
@click.option('--ui-port', default=None, type=int,
|
|
39
|
+
help="Port to listen on for the User Interface")
|
|
40
|
+
@click.option('--with-rabbitmq', 'start_rabbitmq', flag_value=True,
|
|
41
|
+
default=False, help="Start RabbitMQ message broker as local "
|
|
42
|
+
"container - use in development only")
|
|
43
|
+
@click.option('--rabbitmq-image', default=None,
|
|
44
|
+
help="RabbitMQ docker image to use")
|
|
45
|
+
@click.option('--keep/--auto-remove', default=False,
|
|
46
|
+
help="Keep image after server has stopped. Useful for debugging")
|
|
47
|
+
@click.option('--mount-src', default='',
|
|
48
|
+
help="Override vantage6 source code in container with the source"
|
|
49
|
+
" code in this path")
|
|
50
|
+
@click.option('--attach/--detach', default=False,
|
|
51
|
+
help="Print server logs to the console after start")
|
|
52
|
+
@click_insert_context
|
|
53
|
+
def cli_server_start(ctx: ServerContext, ip: str, port: int, image: str,
|
|
54
|
+
start_ui: bool, ui_port: int, start_rabbitmq: bool,
|
|
55
|
+
rabbitmq_image: str, keep: bool, mount_src: str,
|
|
56
|
+
attach: bool) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Start the server.
|
|
59
|
+
"""
|
|
60
|
+
# will print an error if not
|
|
61
|
+
check_docker_running()
|
|
62
|
+
|
|
63
|
+
info("Starting server...")
|
|
64
|
+
info("Finding Docker daemon.")
|
|
65
|
+
docker_client = docker.from_env()
|
|
66
|
+
|
|
67
|
+
# check if name is allowed for docker volume, else exit
|
|
68
|
+
check_config_name_allowed(ctx.name)
|
|
69
|
+
|
|
70
|
+
# check that this server is not already running
|
|
71
|
+
running_servers = docker_client.containers.list(
|
|
72
|
+
filters={"label": f"{APPNAME}-type=server"})
|
|
73
|
+
for server in running_servers:
|
|
74
|
+
if server.name == f"{APPNAME}-{ctx.name}-{ctx.scope}-server":
|
|
75
|
+
error(f"Server {Fore.RED}{ctx.name}{Style.RESET_ALL} "
|
|
76
|
+
"is already running")
|
|
77
|
+
exit(1)
|
|
78
|
+
|
|
79
|
+
# Determine image-name. First we check if the option --image has been used.
|
|
80
|
+
# Then we check if the image has been specified in the config file, and
|
|
81
|
+
# finally we use the default settings from the package.
|
|
82
|
+
if image is None:
|
|
83
|
+
custom_images: dict = ctx.config.get('images')
|
|
84
|
+
if custom_images:
|
|
85
|
+
image = custom_images.get('server')
|
|
86
|
+
if not image:
|
|
87
|
+
image = f"{DEFAULT_DOCKER_REGISTRY}/{DEFAULT_SERVER_IMAGE}"
|
|
88
|
+
|
|
89
|
+
info(f"Pulling latest server image '{image}'.")
|
|
90
|
+
try:
|
|
91
|
+
pull_if_newer(docker.from_env(), image)
|
|
92
|
+
# docker_client.images.pull(image)
|
|
93
|
+
except Exception as e:
|
|
94
|
+
warning(' ... Getting latest server image failed:')
|
|
95
|
+
warning(f" {e}")
|
|
96
|
+
else:
|
|
97
|
+
info(" ... success!")
|
|
98
|
+
|
|
99
|
+
info("Creating mounts")
|
|
100
|
+
config_file = "/mnt/config.yaml"
|
|
101
|
+
mounts = [
|
|
102
|
+
docker.types.Mount(
|
|
103
|
+
config_file, str(ctx.config_file), type="bind"
|
|
104
|
+
)
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
if mount_src:
|
|
108
|
+
mount_src = os.path.abspath(mount_src)
|
|
109
|
+
mounts.append(docker.types.Mount("/vantage6", mount_src, type="bind"))
|
|
110
|
+
# FIXME: code duplication with cli_server_import()
|
|
111
|
+
# try to mount database
|
|
112
|
+
uri = ctx.config['uri']
|
|
113
|
+
url = make_url(uri)
|
|
114
|
+
environment_vars = None
|
|
115
|
+
|
|
116
|
+
# If host is None, we're dealing with a file-based DB, like SQLite
|
|
117
|
+
if (url.host is None):
|
|
118
|
+
db_path = url.database
|
|
119
|
+
|
|
120
|
+
if not os.path.isabs(db_path):
|
|
121
|
+
# We're dealing with a relative path here -> make it absolute
|
|
122
|
+
db_path = ctx.data_dir / url.database
|
|
123
|
+
|
|
124
|
+
basename = os.path.basename(db_path)
|
|
125
|
+
dirname = os.path.dirname(db_path)
|
|
126
|
+
os.makedirs(dirname, exist_ok=True)
|
|
127
|
+
|
|
128
|
+
# we're mounting the entire folder that contains the database
|
|
129
|
+
mounts.append(docker.types.Mount(
|
|
130
|
+
"/mnt/database/", dirname, type="bind"
|
|
131
|
+
))
|
|
132
|
+
|
|
133
|
+
environment_vars = {
|
|
134
|
+
"VANTAGE6_DB_URI": f"sqlite:////mnt/database/{basename}",
|
|
135
|
+
"VANTAGE6_CONFIG_NAME": ctx.config_file_name
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
else:
|
|
139
|
+
warning(f"Database could not be transferred, make sure {url.host} "
|
|
140
|
+
"is reachable from the Docker container")
|
|
141
|
+
info("Consider using the docker-compose method to start a server")
|
|
142
|
+
|
|
143
|
+
# Create a docker network for the server and other services like RabbitMQ
|
|
144
|
+
# to reside in
|
|
145
|
+
server_network_mgr = NetworkManager(
|
|
146
|
+
network_name=f"{APPNAME}-{ctx.name}-{ctx.scope}-network"
|
|
147
|
+
)
|
|
148
|
+
server_network_mgr.create_network(is_internal=False)
|
|
149
|
+
|
|
150
|
+
if start_rabbitmq or ctx.config.get('rabbitmq') and \
|
|
151
|
+
ctx.config['rabbitmq'].get('start_with_server', False):
|
|
152
|
+
# Note that ctx.data_dir has been created at this point, which is
|
|
153
|
+
# required for putting some RabbitMQ configuration files inside
|
|
154
|
+
info('Starting RabbitMQ container')
|
|
155
|
+
_start_rabbitmq(ctx, rabbitmq_image, server_network_mgr)
|
|
156
|
+
elif ctx.config.get('rabbitmq'):
|
|
157
|
+
info("RabbitMQ is provided in the config file as external service. "
|
|
158
|
+
"Assuming this service is up and running.")
|
|
159
|
+
else:
|
|
160
|
+
warning('Message queue disabled! This means that the vantage6 server '
|
|
161
|
+
'cannot be scaled horizontally!')
|
|
162
|
+
|
|
163
|
+
# start the UI if requested
|
|
164
|
+
if start_ui or ctx.config.get('ui') and ctx.config['ui'].get('enabled'):
|
|
165
|
+
_start_ui(docker_client, ctx, ui_port)
|
|
166
|
+
|
|
167
|
+
# The `ip` and `port` refer here to the ip and port within the container.
|
|
168
|
+
# So we do not really care that is it listening on all interfaces.
|
|
169
|
+
internal_port = 5000
|
|
170
|
+
cmd = (
|
|
171
|
+
f'uwsgi --http :{internal_port} --gevent 1000 --http-websockets '
|
|
172
|
+
'--master --callable app --disable-logging '
|
|
173
|
+
'--wsgi-file /vantage6/vantage6-server/vantage6/server/wsgi.py '
|
|
174
|
+
f'--pyargv {config_file}'
|
|
175
|
+
)
|
|
176
|
+
info(cmd)
|
|
177
|
+
|
|
178
|
+
info("Run Docker container")
|
|
179
|
+
port_ = str(port or ctx.config["port"] or 5000)
|
|
180
|
+
container = docker_client.containers.run(
|
|
181
|
+
image,
|
|
182
|
+
command=cmd,
|
|
183
|
+
mounts=mounts,
|
|
184
|
+
detach=True,
|
|
185
|
+
labels={
|
|
186
|
+
f"{APPNAME}-type": "server",
|
|
187
|
+
"name": ctx.config_file_name
|
|
188
|
+
},
|
|
189
|
+
environment=environment_vars,
|
|
190
|
+
ports={f"{internal_port}/tcp": (ip, port_)},
|
|
191
|
+
name=ctx.docker_container_name,
|
|
192
|
+
auto_remove=not keep,
|
|
193
|
+
tty=True,
|
|
194
|
+
network=server_network_mgr.network_name
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
info(f"Success! container id = {container.id}")
|
|
198
|
+
|
|
199
|
+
if attach:
|
|
200
|
+
logs = container.attach(stream=True, logs=True, stdout=True)
|
|
201
|
+
Thread(target=print_log_worker, args=(logs,), daemon=True).start()
|
|
202
|
+
while True:
|
|
203
|
+
try:
|
|
204
|
+
time.sleep(1)
|
|
205
|
+
except KeyboardInterrupt:
|
|
206
|
+
info("Closing log file. Keyboard Interrupt.")
|
|
207
|
+
info("Note that your server is still running! Shut it down "
|
|
208
|
+
f"with {Fore.RED}v6 server stop{Style.RESET_ALL}")
|
|
209
|
+
exit(0)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def _start_rabbitmq(ctx: ServerContext, rabbitmq_image: str,
|
|
213
|
+
network_mgr: NetworkManager) -> None:
|
|
214
|
+
"""
|
|
215
|
+
Start the RabbitMQ container if it is not already running.
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
ctx : ServerContext
|
|
220
|
+
Server context object
|
|
221
|
+
rabbitmq_image : str
|
|
222
|
+
RabbitMQ image to use
|
|
223
|
+
network_mgr : NetworkManager
|
|
224
|
+
Network manager object
|
|
225
|
+
"""
|
|
226
|
+
rabbit_uri = ctx.config['rabbitmq'].get('uri')
|
|
227
|
+
if not rabbit_uri:
|
|
228
|
+
error("No RabbitMQ URI found in the configuration file! Please add"
|
|
229
|
+
"a 'uri' key to the 'rabbitmq' section of the configuration.")
|
|
230
|
+
exit(1)
|
|
231
|
+
# kick off RabbitMQ container
|
|
232
|
+
rabbit_mgr = RabbitMQManager(
|
|
233
|
+
ctx=ctx, network_mgr=network_mgr, image=rabbitmq_image)
|
|
234
|
+
rabbit_mgr.start()
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _start_ui(client: DockerClient, ctx: ServerContext, ui_port: int) -> None:
|
|
238
|
+
"""
|
|
239
|
+
Start the UI container.
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
client : DockerClient
|
|
244
|
+
Docker client
|
|
245
|
+
ctx : ServerContext
|
|
246
|
+
Server context object
|
|
247
|
+
ui_port : int
|
|
248
|
+
Port to expose the UI on
|
|
249
|
+
"""
|
|
250
|
+
# if no port is specified, check if config contains a port
|
|
251
|
+
ui_config = ctx.config.get('ui')
|
|
252
|
+
if ui_config and not ui_port:
|
|
253
|
+
ui_port = ui_config.get('port')
|
|
254
|
+
|
|
255
|
+
# check if the port is valid
|
|
256
|
+
# TODO make function to check if port is valid, and use in more places
|
|
257
|
+
if not isinstance(ui_port, int) or not 0 < ui_port < 65536:
|
|
258
|
+
warning(f"UI port '{ui_port}' is not valid! Using default port "
|
|
259
|
+
f"{DEFAULT_UI_PORT}")
|
|
260
|
+
ui_port = DEFAULT_UI_PORT
|
|
261
|
+
|
|
262
|
+
# find image to use
|
|
263
|
+
custom_images: dict = ctx.config.get('images')
|
|
264
|
+
image = None
|
|
265
|
+
if custom_images:
|
|
266
|
+
image = custom_images.get('ui')
|
|
267
|
+
if not image:
|
|
268
|
+
image = f"{DEFAULT_DOCKER_REGISTRY}/{DEFAULT_UI_IMAGE}"
|
|
269
|
+
|
|
270
|
+
info(f"Pulling latest UI image '{image}'.")
|
|
271
|
+
try:
|
|
272
|
+
pull_if_newer(docker.from_env(), image)
|
|
273
|
+
# docker_client.images.pull(image)
|
|
274
|
+
except Exception as e:
|
|
275
|
+
warning(' ... Getting latest node image failed:')
|
|
276
|
+
warning(f" {e}")
|
|
277
|
+
else:
|
|
278
|
+
info(" ... success!")
|
|
279
|
+
|
|
280
|
+
# set environment variables
|
|
281
|
+
env_vars = {
|
|
282
|
+
"SERVER_URL": f"http://localhost:{ctx.config.get('port')}",
|
|
283
|
+
"API_PATH": ctx.config.get("api_path"),
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
# stop the UI container if it is already running
|
|
287
|
+
stop_ui(client, ctx)
|
|
288
|
+
|
|
289
|
+
info(f'Starting User Interface at port {ui_port}')
|
|
290
|
+
ui_container_name = f"{APPNAME}-{ctx.name}-{ctx.scope}-ui"
|
|
291
|
+
client.containers.run(
|
|
292
|
+
image,
|
|
293
|
+
detach=True,
|
|
294
|
+
labels={
|
|
295
|
+
f"{APPNAME}-type": "ui",
|
|
296
|
+
"name": ctx.config_file_name
|
|
297
|
+
},
|
|
298
|
+
ports={"80/tcp": (ctx.config.get('ip'), ui_port)},
|
|
299
|
+
name=ui_container_name,
|
|
300
|
+
environment=env_vars,
|
|
301
|
+
tty=True,
|
|
302
|
+
)
|