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.

Files changed (64) hide show
  1. tests_cli/test_example.py +0 -1
  2. tests_cli/test_node_cli.py +74 -79
  3. tests_cli/test_server_cli.py +22 -22
  4. tests_cli/test_wizard.py +41 -29
  5. vantage6/cli/__build__ +1 -1
  6. vantage6/cli/_version.py +11 -8
  7. vantage6/cli/algorithm/create.py +14 -14
  8. vantage6/cli/algorithm/update.py +9 -6
  9. vantage6/cli/algostore/attach.py +32 -0
  10. vantage6/cli/algostore/new.py +55 -0
  11. vantage6/cli/algostore/start.py +102 -0
  12. vantage6/cli/algostore/stop.py +60 -0
  13. vantage6/cli/cli.py +32 -12
  14. vantage6/cli/common/decorator.py +92 -0
  15. vantage6/cli/common/start.py +232 -0
  16. vantage6/cli/configuration_manager.py +22 -32
  17. vantage6/cli/configuration_wizard.py +255 -193
  18. vantage6/cli/context/__init__.py +86 -0
  19. vantage6/cli/context/algorithm_store.py +130 -0
  20. vantage6/cli/context/base_server.py +89 -0
  21. vantage6/cli/context/node.py +254 -0
  22. vantage6/cli/context/server.py +127 -0
  23. vantage6/cli/dev/create.py +180 -113
  24. vantage6/cli/dev/remove.py +20 -19
  25. vantage6/cli/dev/start.py +10 -10
  26. vantage6/cli/dev/stop.py +7 -5
  27. vantage6/cli/globals.py +24 -0
  28. vantage6/cli/node/attach.py +21 -10
  29. vantage6/cli/node/clean.py +4 -2
  30. vantage6/cli/node/common/__init__.py +15 -11
  31. vantage6/cli/node/create_private_key.py +58 -27
  32. vantage6/cli/node/files.py +14 -6
  33. vantage6/cli/node/list.py +18 -24
  34. vantage6/cli/node/new.py +21 -12
  35. vantage6/cli/node/remove.py +31 -22
  36. vantage6/cli/node/set_api_key.py +18 -12
  37. vantage6/cli/node/start.py +38 -12
  38. vantage6/cli/node/stop.py +32 -18
  39. vantage6/cli/node/version.py +23 -13
  40. vantage6/cli/rabbitmq/__init__.py +9 -9
  41. vantage6/cli/rabbitmq/definitions.py +24 -28
  42. vantage6/cli/rabbitmq/queue_manager.py +37 -40
  43. vantage6/cli/server/attach.py +16 -11
  44. vantage6/cli/server/common/__init__.py +37 -25
  45. vantage6/cli/server/files.py +1 -1
  46. vantage6/cli/server/import_.py +45 -37
  47. vantage6/cli/server/list.py +22 -23
  48. vantage6/cli/server/new.py +20 -14
  49. vantage6/cli/server/remove.py +5 -4
  50. vantage6/cli/server/shell.py +17 -6
  51. vantage6/cli/server/start.py +118 -174
  52. vantage6/cli/server/stop.py +31 -23
  53. vantage6/cli/server/version.py +16 -13
  54. vantage6/cli/test/common/diagnostic_runner.py +30 -34
  55. vantage6/cli/test/feature_tester.py +51 -25
  56. vantage6/cli/test/integration_test.py +69 -29
  57. vantage6/cli/utils.py +6 -5
  58. {vantage6-4.2.1.dist-info → vantage6-4.3.0b3.dist-info}/METADATA +5 -3
  59. vantage6-4.3.0b3.dist-info/RECORD +68 -0
  60. vantage6/cli/context.py +0 -416
  61. vantage6-4.2.1.dist-info/RECORD +0 -58
  62. {vantage6-4.2.1.dist-info → vantage6-4.3.0b3.dist-info}/WHEEL +0 -0
  63. {vantage6-4.2.1.dist-info → vantage6-4.3.0b3.dist-info}/entry_points.txt +0 -0
  64. {vantage6-4.2.1.dist-info → vantage6-4.3.0b3.dist-info}/top_level.txt +0 -0
@@ -9,8 +9,14 @@ from vantage6.cli.utils import info
9
9
 
10
10
 
11
11
  @click.command()
12
- @click.option('-d', '--dir', 'directory', default=None, type=str,
13
- help="Directory to put the algorithm into")
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='server')
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='attach')
44
- cli_server.add_command(cli_server_files, name='files')
45
- cli_server.add_command(cli_server_import, name='import')
46
- cli_server.add_command(cli_server_configuration_list, name='list')
47
- cli_server.add_command(cli_server_new, name='new')
48
- cli_server.add_command(cli_server_remove, name='remove')
49
- cli_server.add_command(cli_server_shell, name='shell')
50
- cli_server.add_command(cli_server_start, name='start')
51
- cli_server.add_command(cli_server_stop, name='stop')
52
- cli_server.add_command(cli_server_version, name='version')
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='cli')
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)