vantage6 4.0.3__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.3.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 -1097
- vantage6/cli/server.py +0 -1033
- vantage6-4.0.3.dist-info/RECORD +0 -28
- vantage6-4.0.3.dist-info/entry_points.txt +0 -4
- {vantage6-4.0.3.dist-info → vantage6-4.1.0b0.dist-info}/WHEEL +0 -0
- {vantage6-4.0.3.dist-info → vantage6-4.1.0b0.dist-info}/top_level.txt +0 -0
vantage6/cli/server.py
DELETED
|
@@ -1,1033 +0,0 @@
|
|
|
1
|
-
import click
|
|
2
|
-
import questionary as q
|
|
3
|
-
import docker
|
|
4
|
-
import os
|
|
5
|
-
import time
|
|
6
|
-
import subprocess
|
|
7
|
-
import itertools
|
|
8
|
-
|
|
9
|
-
from typing import Iterable
|
|
10
|
-
from threading import Thread
|
|
11
|
-
from functools import wraps
|
|
12
|
-
from colorama import (Fore, Style)
|
|
13
|
-
from sqlalchemy.engine.url import make_url
|
|
14
|
-
from docker.client import DockerClient
|
|
15
|
-
|
|
16
|
-
from vantage6.common import (info, warning, error, debug as debug_msg,
|
|
17
|
-
check_config_writeable)
|
|
18
|
-
from vantage6.common.docker.addons import (
|
|
19
|
-
pull_if_newer, check_docker_running, remove_container,
|
|
20
|
-
get_server_config_name, get_container, get_num_nonempty_networks,
|
|
21
|
-
get_network, delete_network, remove_container_if_exists
|
|
22
|
-
)
|
|
23
|
-
from vantage6.common.docker.network_manager import NetworkManager
|
|
24
|
-
from vantage6.common.globals import (
|
|
25
|
-
APPNAME,
|
|
26
|
-
STRING_ENCODING,
|
|
27
|
-
DEFAULT_DOCKER_REGISTRY,
|
|
28
|
-
DEFAULT_SERVER_IMAGE,
|
|
29
|
-
DEFAULT_UI_IMAGE
|
|
30
|
-
)
|
|
31
|
-
from vantage6.cli.rabbitmq import split_rabbitmq_uri
|
|
32
|
-
|
|
33
|
-
from vantage6.cli.globals import (
|
|
34
|
-
DEFAULT_SERVER_SYSTEM_FOLDERS, DEFAULT_UI_PORT
|
|
35
|
-
)
|
|
36
|
-
from vantage6.cli.context import ServerContext
|
|
37
|
-
from vantage6.cli.configuration_wizard import (
|
|
38
|
-
select_configuration_questionaire,
|
|
39
|
-
configuration_wizard
|
|
40
|
-
)
|
|
41
|
-
from vantage6.cli.utils import (
|
|
42
|
-
check_config_name_allowed,
|
|
43
|
-
prompt_config_name,
|
|
44
|
-
remove_file
|
|
45
|
-
)
|
|
46
|
-
from vantage6.cli.rabbitmq.queue_manager import RabbitMQManager
|
|
47
|
-
from vantage6.cli import __version__
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def click_insert_context(func: callable) -> callable:
|
|
51
|
-
"""
|
|
52
|
-
Supply the Click function with additional context parameters. The context
|
|
53
|
-
is then passed to the function as the first argument.
|
|
54
|
-
|
|
55
|
-
Parameters
|
|
56
|
-
----------
|
|
57
|
-
func : Callable
|
|
58
|
-
function you want the context to be passed to
|
|
59
|
-
|
|
60
|
-
Returns
|
|
61
|
-
-------
|
|
62
|
-
Callable
|
|
63
|
-
Click function with context
|
|
64
|
-
"""
|
|
65
|
-
@click.option('-n', '--name', default=None,
|
|
66
|
-
help="Name of the configuration you want to use.")
|
|
67
|
-
@click.option('-c', '--config', default=None,
|
|
68
|
-
help='Absolute path to configuration-file; overrides NAME')
|
|
69
|
-
@click.option('--system', 'system_folders', flag_value=True,
|
|
70
|
-
help='Use system folders instead of user folders. This is '
|
|
71
|
-
'the default')
|
|
72
|
-
@click.option('--user', 'system_folders', flag_value=False,
|
|
73
|
-
default=DEFAULT_SERVER_SYSTEM_FOLDERS,
|
|
74
|
-
help='Use user folders instead of system folders')
|
|
75
|
-
@wraps(func)
|
|
76
|
-
def func_with_context(name: str, config: str, system_folders: bool, *args,
|
|
77
|
-
**kwargs) -> callable:
|
|
78
|
-
"""
|
|
79
|
-
Decorator function that adds the context to the function.
|
|
80
|
-
|
|
81
|
-
Returns
|
|
82
|
-
-------
|
|
83
|
-
Callable
|
|
84
|
-
Decorated function
|
|
85
|
-
"""
|
|
86
|
-
# path to configuration file always overrides name
|
|
87
|
-
if config:
|
|
88
|
-
ctx = ServerContext.from_external_config_file(
|
|
89
|
-
config,
|
|
90
|
-
system_folders
|
|
91
|
-
)
|
|
92
|
-
return func(ctx, *args, **kwargs)
|
|
93
|
-
|
|
94
|
-
# in case no name is supplied, ask the user to select one
|
|
95
|
-
if not name:
|
|
96
|
-
try:
|
|
97
|
-
# select configuration if none supplied
|
|
98
|
-
name = select_configuration_questionaire(
|
|
99
|
-
"server", system_folders
|
|
100
|
-
)
|
|
101
|
-
except Exception:
|
|
102
|
-
error("No configurations could be found!")
|
|
103
|
-
exit(1)
|
|
104
|
-
|
|
105
|
-
ctx = get_server_context(name, system_folders)
|
|
106
|
-
return func(ctx, *args, **kwargs)
|
|
107
|
-
|
|
108
|
-
return func_with_context
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
@click.group(name='server')
|
|
112
|
-
def cli_server() -> None:
|
|
113
|
-
"""
|
|
114
|
-
The `vserver` commands allow you to manage your vantage6 server instances.
|
|
115
|
-
"""
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
#
|
|
119
|
-
# start
|
|
120
|
-
#
|
|
121
|
-
@cli_server.command(name='start')
|
|
122
|
-
@click.option('--ip', default=None, help='IP address to listen on')
|
|
123
|
-
@click.option('-p', '--port', default=None, type=int, help='Port to listen on')
|
|
124
|
-
@click.option('-i', '--image', default=None, help="Server Docker image to use")
|
|
125
|
-
@click.option('--with-ui', 'start_ui', flag_value=True, default=False,
|
|
126
|
-
help="Start the graphical User Interface as well")
|
|
127
|
-
@click.option('--ui-port', default=None, type=int,
|
|
128
|
-
help="Port to listen on for the User Interface")
|
|
129
|
-
@click.option('--with-rabbitmq', 'start_rabbitmq', flag_value=True,
|
|
130
|
-
default=False, help="Start RabbitMQ message broker as local "
|
|
131
|
-
"container - use in development only")
|
|
132
|
-
@click.option('--rabbitmq-image', default=None,
|
|
133
|
-
help="RabbitMQ docker image to use")
|
|
134
|
-
@click.option('--keep/--auto-remove', default=False,
|
|
135
|
-
help="Keep image after server has stopped. Useful for debugging")
|
|
136
|
-
@click.option('--mount-src', default='',
|
|
137
|
-
help="Override vantage6 source code in container with the source"
|
|
138
|
-
" code in this path")
|
|
139
|
-
@click.option('--attach/--detach', default=False,
|
|
140
|
-
help="Print server logs to the console after start")
|
|
141
|
-
@click_insert_context
|
|
142
|
-
def cli_server_start(ctx: ServerContext, ip: str, port: int, image: str,
|
|
143
|
-
start_ui: bool, ui_port: int, start_rabbitmq: bool,
|
|
144
|
-
rabbitmq_image: str, keep: bool, mount_src: str,
|
|
145
|
-
attach: bool) -> None:
|
|
146
|
-
"""
|
|
147
|
-
Start the server.
|
|
148
|
-
"""
|
|
149
|
-
vserver_start(ctx, ip, port, image, start_ui, ui_port, start_rabbitmq,
|
|
150
|
-
rabbitmq_image, keep, mount_src, attach)
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def vserver_start(ctx: ServerContext, ip: str, port: int, image: str,
|
|
154
|
-
start_ui: bool, ui_port: int, start_rabbitmq: bool,
|
|
155
|
-
rabbitmq_image: str, keep: bool, mount_src: str,
|
|
156
|
-
attach: bool) -> None:
|
|
157
|
-
"""
|
|
158
|
-
Start the server in a Docker container.
|
|
159
|
-
|
|
160
|
-
Parameters
|
|
161
|
-
----------
|
|
162
|
-
ctx : ServerContext
|
|
163
|
-
Server context object
|
|
164
|
-
ip : str
|
|
165
|
-
ip interface to listen on
|
|
166
|
-
port : int
|
|
167
|
-
port to listen on
|
|
168
|
-
image : str
|
|
169
|
-
Server Docker image to use
|
|
170
|
-
start_ui : bool
|
|
171
|
-
Start the graphical User Interface as well
|
|
172
|
-
ui_port : int
|
|
173
|
-
Port to listen on for the User Interface
|
|
174
|
-
start_rabbitmq : bool
|
|
175
|
-
Start RabbitMQ message broker as local container - use only in
|
|
176
|
-
development
|
|
177
|
-
rabbitmq_image : str
|
|
178
|
-
RabbitMQ docker image to use
|
|
179
|
-
keep : bool
|
|
180
|
-
Wether to keep the image after the server has finished, useful for
|
|
181
|
-
debugging
|
|
182
|
-
mount_src : str
|
|
183
|
-
Path to the vantage6 package source, this overrides the source code in
|
|
184
|
-
the container. This is useful when developing and testing the server.
|
|
185
|
-
attach : bool
|
|
186
|
-
Wether to attach the server logs to the console after starting the
|
|
187
|
-
server.
|
|
188
|
-
"""
|
|
189
|
-
# will print an error if not
|
|
190
|
-
check_docker_running()
|
|
191
|
-
|
|
192
|
-
info("Starting server...")
|
|
193
|
-
info("Finding Docker daemon.")
|
|
194
|
-
docker_client = docker.from_env()
|
|
195
|
-
|
|
196
|
-
# check if name is allowed for docker volume, else exit
|
|
197
|
-
check_config_name_allowed(ctx.name)
|
|
198
|
-
|
|
199
|
-
# check that this server is not already running
|
|
200
|
-
running_servers = docker_client.containers.list(
|
|
201
|
-
filters={"label": f"{APPNAME}-type=server"})
|
|
202
|
-
for server in running_servers:
|
|
203
|
-
if server.name == f"{APPNAME}-{ctx.name}-{ctx.scope}-server":
|
|
204
|
-
error(f"Server {Fore.RED}{ctx.name}{Style.RESET_ALL} "
|
|
205
|
-
"is already running")
|
|
206
|
-
exit(1)
|
|
207
|
-
|
|
208
|
-
# Determine image-name. First we check if the option --image has been used.
|
|
209
|
-
# Then we check if the image has been specified in the config file, and
|
|
210
|
-
# finally we use the default settings from the package.
|
|
211
|
-
if image is None:
|
|
212
|
-
custom_images: dict = ctx.config.get('images')
|
|
213
|
-
if custom_images:
|
|
214
|
-
image = custom_images.get('server')
|
|
215
|
-
if not image:
|
|
216
|
-
image = f"{DEFAULT_DOCKER_REGISTRY}/{DEFAULT_SERVER_IMAGE}"
|
|
217
|
-
|
|
218
|
-
info(f"Pulling latest server image '{image}'.")
|
|
219
|
-
try:
|
|
220
|
-
pull_if_newer(docker.from_env(), image)
|
|
221
|
-
# docker_client.images.pull(image)
|
|
222
|
-
except Exception as e:
|
|
223
|
-
warning(' ... Getting latest server image failed:')
|
|
224
|
-
warning(f" {e}")
|
|
225
|
-
else:
|
|
226
|
-
info(" ... success!")
|
|
227
|
-
|
|
228
|
-
info("Creating mounts")
|
|
229
|
-
config_file = "/mnt/config.yaml"
|
|
230
|
-
mounts = [
|
|
231
|
-
docker.types.Mount(
|
|
232
|
-
config_file, str(ctx.config_file), type="bind"
|
|
233
|
-
)
|
|
234
|
-
]
|
|
235
|
-
|
|
236
|
-
if mount_src:
|
|
237
|
-
mount_src = os.path.abspath(mount_src)
|
|
238
|
-
mounts.append(docker.types.Mount("/vantage6", mount_src, type="bind"))
|
|
239
|
-
# FIXME: code duplication with cli_server_import()
|
|
240
|
-
# try to mount database
|
|
241
|
-
uri = ctx.config['uri']
|
|
242
|
-
url = make_url(uri)
|
|
243
|
-
environment_vars = None
|
|
244
|
-
|
|
245
|
-
# If host is None, we're dealing with a file-based DB, like SQLite
|
|
246
|
-
if (url.host is None):
|
|
247
|
-
db_path = url.database
|
|
248
|
-
|
|
249
|
-
if not os.path.isabs(db_path):
|
|
250
|
-
# We're dealing with a relative path here -> make it absolute
|
|
251
|
-
db_path = ctx.data_dir / url.database
|
|
252
|
-
|
|
253
|
-
basename = os.path.basename(db_path)
|
|
254
|
-
dirname = os.path.dirname(db_path)
|
|
255
|
-
os.makedirs(dirname, exist_ok=True)
|
|
256
|
-
|
|
257
|
-
# we're mounting the entire folder that contains the database
|
|
258
|
-
mounts.append(docker.types.Mount(
|
|
259
|
-
"/mnt/database/", dirname, type="bind"
|
|
260
|
-
))
|
|
261
|
-
|
|
262
|
-
environment_vars = {
|
|
263
|
-
"VANTAGE6_DB_URI": f"sqlite:////mnt/database/{basename}",
|
|
264
|
-
"VANTAGE6_CONFIG_NAME": ctx.config_file_name
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
else:
|
|
268
|
-
warning(f"Database could not be transferred, make sure {url.host} "
|
|
269
|
-
"is reachable from the Docker container")
|
|
270
|
-
info("Consider using the docker-compose method to start a server")
|
|
271
|
-
|
|
272
|
-
# Create a docker network for the server and other services like RabbitMQ
|
|
273
|
-
# to reside in
|
|
274
|
-
server_network_mgr = NetworkManager(
|
|
275
|
-
network_name=f"{APPNAME}-{ctx.name}-{ctx.scope}-network"
|
|
276
|
-
)
|
|
277
|
-
server_network_mgr.create_network(is_internal=False)
|
|
278
|
-
|
|
279
|
-
if start_rabbitmq or ctx.config.get('rabbitmq') and \
|
|
280
|
-
ctx.config['rabbitmq'].get('start_with_server', False):
|
|
281
|
-
# Note that ctx.data_dir has been created at this point, which is
|
|
282
|
-
# required for putting some RabbitMQ configuration files inside
|
|
283
|
-
info('Starting RabbitMQ container')
|
|
284
|
-
_start_rabbitmq(ctx, rabbitmq_image, server_network_mgr)
|
|
285
|
-
elif ctx.config.get('rabbitmq'):
|
|
286
|
-
info("RabbitMQ is provided in the config file as external service. "
|
|
287
|
-
"Assuming this service is up and running.")
|
|
288
|
-
else:
|
|
289
|
-
warning('Message queue disabled! This means that the vantage6 server '
|
|
290
|
-
'cannot be scaled horizontally!')
|
|
291
|
-
|
|
292
|
-
# start the UI if requested
|
|
293
|
-
if start_ui or ctx.config.get('ui') and ctx.config['ui'].get('enabled'):
|
|
294
|
-
_start_ui(docker_client, ctx, ui_port)
|
|
295
|
-
|
|
296
|
-
# The `ip` and `port` refer here to the ip and port within the container.
|
|
297
|
-
# So we do not really care that is it listening on all interfaces.
|
|
298
|
-
internal_port = 5000
|
|
299
|
-
cmd = (
|
|
300
|
-
f'uwsgi --http :{internal_port} --gevent 1000 --http-websockets '
|
|
301
|
-
'--master --callable app --disable-logging '
|
|
302
|
-
'--wsgi-file /vantage6/vantage6-server/vantage6/server/wsgi.py '
|
|
303
|
-
f'--pyargv {config_file}'
|
|
304
|
-
)
|
|
305
|
-
info(cmd)
|
|
306
|
-
|
|
307
|
-
info("Run Docker container")
|
|
308
|
-
port_ = str(port or ctx.config["port"] or 5000)
|
|
309
|
-
container = docker_client.containers.run(
|
|
310
|
-
image,
|
|
311
|
-
command=cmd,
|
|
312
|
-
mounts=mounts,
|
|
313
|
-
detach=True,
|
|
314
|
-
labels={
|
|
315
|
-
f"{APPNAME}-type": "server",
|
|
316
|
-
"name": ctx.config_file_name
|
|
317
|
-
},
|
|
318
|
-
environment=environment_vars,
|
|
319
|
-
ports={f"{internal_port}/tcp": (ip, port_)},
|
|
320
|
-
name=ctx.docker_container_name,
|
|
321
|
-
auto_remove=not keep,
|
|
322
|
-
tty=True,
|
|
323
|
-
network=server_network_mgr.network_name
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
info(f"Success! container id = {container.id}")
|
|
327
|
-
|
|
328
|
-
if attach:
|
|
329
|
-
logs = container.attach(stream=True, logs=True, stdout=True)
|
|
330
|
-
Thread(target=_print_log_worker, args=(logs,), daemon=True).start()
|
|
331
|
-
while True:
|
|
332
|
-
try:
|
|
333
|
-
time.sleep(1)
|
|
334
|
-
except KeyboardInterrupt:
|
|
335
|
-
info("Closing log file. Keyboard Interrupt.")
|
|
336
|
-
info("Note that your server is still running! Shut it down "
|
|
337
|
-
f"with {Fore.RED}vserver stop{Style.RESET_ALL}")
|
|
338
|
-
exit(0)
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
#
|
|
342
|
-
# list
|
|
343
|
-
#
|
|
344
|
-
@cli_server.command(name='list')
|
|
345
|
-
def cli_server_configuration_list() -> None:
|
|
346
|
-
"""
|
|
347
|
-
Print the available server configurations.
|
|
348
|
-
"""
|
|
349
|
-
check_docker_running()
|
|
350
|
-
client = docker.from_env()
|
|
351
|
-
|
|
352
|
-
running_server = client.containers.list(
|
|
353
|
-
filters={"label": f"{APPNAME}-type=server"})
|
|
354
|
-
running_node_names = []
|
|
355
|
-
for node in running_server:
|
|
356
|
-
running_node_names.append(node.name)
|
|
357
|
-
|
|
358
|
-
header = \
|
|
359
|
-
"\nName"+(21*" ") + \
|
|
360
|
-
"Status"+(10*" ") + \
|
|
361
|
-
"System/User"
|
|
362
|
-
|
|
363
|
-
click.echo(header)
|
|
364
|
-
click.echo("-"*len(header))
|
|
365
|
-
|
|
366
|
-
running = Fore.GREEN + "Running" + Style.RESET_ALL
|
|
367
|
-
stopped = Fore.RED + "Not running" + Style.RESET_ALL
|
|
368
|
-
|
|
369
|
-
# system folders
|
|
370
|
-
configs, f1 = ServerContext.available_configurations(system_folders=True)
|
|
371
|
-
for config in configs:
|
|
372
|
-
status = running if f"{APPNAME}-{config.name}-system-server" in \
|
|
373
|
-
running_node_names else stopped
|
|
374
|
-
click.echo(
|
|
375
|
-
f"{config.name:25}"
|
|
376
|
-
f"{status:25} System "
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
# user folders
|
|
380
|
-
configs, f2 = ServerContext.available_configurations(system_folders=False)
|
|
381
|
-
for config in configs:
|
|
382
|
-
status = running if f"{APPNAME}-{config.name}-user-server" in \
|
|
383
|
-
running_node_names else stopped
|
|
384
|
-
click.echo(
|
|
385
|
-
f"{config.name:25}"
|
|
386
|
-
f"{status:25} User "
|
|
387
|
-
)
|
|
388
|
-
|
|
389
|
-
click.echo("-"*85)
|
|
390
|
-
if len(f1)+len(f2):
|
|
391
|
-
warning(
|
|
392
|
-
f"{Fore.RED}Failed imports: {len(f1)+len(f2)}{Style.RESET_ALL}")
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
#
|
|
396
|
-
# files
|
|
397
|
-
#
|
|
398
|
-
@cli_server.command(name='files')
|
|
399
|
-
@click_insert_context
|
|
400
|
-
def cli_server_files(ctx: ServerContext) -> None:
|
|
401
|
-
"""
|
|
402
|
-
List files that belong to a particular server instance.
|
|
403
|
-
"""
|
|
404
|
-
info(f"Configuration file = {ctx.config_file}")
|
|
405
|
-
info(f"Log file = {ctx.log_file}")
|
|
406
|
-
info(f"Database = {ctx.get_database_uri()}")
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
#
|
|
410
|
-
# new
|
|
411
|
-
#
|
|
412
|
-
@cli_server.command(name='new')
|
|
413
|
-
@click.option('-n', '--name', default=None,
|
|
414
|
-
help="name of the configuration you want to use.")
|
|
415
|
-
@click.option('--system', 'system_folders', flag_value=True)
|
|
416
|
-
@click.option('--user', 'system_folders', flag_value=False,
|
|
417
|
-
default=DEFAULT_SERVER_SYSTEM_FOLDERS)
|
|
418
|
-
def cli_server_new(name: str, system_folders: bool) -> None:
|
|
419
|
-
"""
|
|
420
|
-
Create a new server configuration.
|
|
421
|
-
"""
|
|
422
|
-
name = prompt_config_name(name)
|
|
423
|
-
|
|
424
|
-
# check if name is allowed for docker volume, else exit
|
|
425
|
-
check_config_name_allowed(name)
|
|
426
|
-
|
|
427
|
-
# check that this config does not exist
|
|
428
|
-
try:
|
|
429
|
-
if ServerContext.config_exists(name, system_folders):
|
|
430
|
-
error(f"Configuration {Fore.RED}{name}{Style.RESET_ALL} already "
|
|
431
|
-
"exists!")
|
|
432
|
-
exit(1)
|
|
433
|
-
except Exception as e:
|
|
434
|
-
error(e)
|
|
435
|
-
exit(1)
|
|
436
|
-
|
|
437
|
-
# Check that we can write in this folder
|
|
438
|
-
if not check_config_writeable(system_folders):
|
|
439
|
-
error("Your user does not have write access to all folders. Exiting")
|
|
440
|
-
info(f"Create a new server using '{Fore.GREEN}vserver new "
|
|
441
|
-
f"--user{Style.RESET_ALL}' instead!")
|
|
442
|
-
exit(1)
|
|
443
|
-
|
|
444
|
-
# create config in ctx location
|
|
445
|
-
cfg_file = configuration_wizard("server", name, system_folders)
|
|
446
|
-
info(f"New configuration created: {Fore.GREEN}{cfg_file}{Style.RESET_ALL}")
|
|
447
|
-
|
|
448
|
-
# info(f"root user created.")
|
|
449
|
-
flag = "" if system_folders else "--user"
|
|
450
|
-
info(f"You can start the server by running {Fore.GREEN}vserver start "
|
|
451
|
-
f"{flag}{Style.RESET_ALL}")
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
#
|
|
455
|
-
# import
|
|
456
|
-
#
|
|
457
|
-
# TODO this method has a lot of duplicated code from `start`
|
|
458
|
-
@cli_server.command(name='import')
|
|
459
|
-
@click.argument('file', type=click.Path(exists=True))
|
|
460
|
-
@click.option('--drop-all', is_flag=True, default=False,
|
|
461
|
-
help="Drop all existing data before importing")
|
|
462
|
-
@click.option('-i', '--image', default=None, help="Node Docker image to use")
|
|
463
|
-
@click.option('--mount-src', default='',
|
|
464
|
-
help="Override vantage6 source code in container with the source"
|
|
465
|
-
" code in this path")
|
|
466
|
-
@click.option('--keep/--auto-remove', default=False,
|
|
467
|
-
help="Keep image after finishing. Useful for debugging")
|
|
468
|
-
@click.option('--wait', default=False, help="Wait for the import to finish")
|
|
469
|
-
@click_insert_context
|
|
470
|
-
def cli_server_import(
|
|
471
|
-
ctx: ServerContext, file: str, drop_all: bool, image: str, mount_src: str,
|
|
472
|
-
keep: bool, wait: bool
|
|
473
|
-
) -> None:
|
|
474
|
-
"""
|
|
475
|
-
Import vantage6 resources, such as organizations, collaborations, users and
|
|
476
|
-
tasks into a server instance.
|
|
477
|
-
|
|
478
|
-
The FILE_ argument should be a path to a yaml file containing the vantage6
|
|
479
|
-
formatted data to import.
|
|
480
|
-
"""
|
|
481
|
-
vserver_import(ctx, file, drop_all, image, mount_src, keep,
|
|
482
|
-
wait)
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
def vserver_import(ctx: ServerContext, file: str, drop_all: bool,
|
|
486
|
-
image: str, mount_src: str, keep: bool, wait: bool) -> None:
|
|
487
|
-
"""Batch import organizations/collaborations/users and tasks.
|
|
488
|
-
|
|
489
|
-
Parameters
|
|
490
|
-
----------
|
|
491
|
-
ctx : ServerContext
|
|
492
|
-
Server context object
|
|
493
|
-
file : str
|
|
494
|
-
Yaml file containing the vantage6 formatted data to import
|
|
495
|
-
drop_all : bool
|
|
496
|
-
Wether to drop all data before importing
|
|
497
|
-
image : str
|
|
498
|
-
Node Docker image to use which contains the import script
|
|
499
|
-
mount_src : str
|
|
500
|
-
Vantage6 source location, this will overwrite the source code in the
|
|
501
|
-
container. Useful for debugging/development.
|
|
502
|
-
keep : bool
|
|
503
|
-
Wether to keep the image after finishing/crashing. Useful for
|
|
504
|
-
debugging.
|
|
505
|
-
wait : bool
|
|
506
|
-
Wether to wait for the import to finish before exiting this function
|
|
507
|
-
"""
|
|
508
|
-
# will print an error if not
|
|
509
|
-
check_docker_running()
|
|
510
|
-
|
|
511
|
-
info("Starting server...")
|
|
512
|
-
info("Finding Docker daemon.")
|
|
513
|
-
docker_client = docker.from_env()
|
|
514
|
-
|
|
515
|
-
# check if name is allowed for docker volume, else exit
|
|
516
|
-
check_config_name_allowed(ctx.name)
|
|
517
|
-
|
|
518
|
-
# pull latest Docker image
|
|
519
|
-
if image is None:
|
|
520
|
-
image = ctx.config.get(
|
|
521
|
-
"image",
|
|
522
|
-
f"{DEFAULT_DOCKER_REGISTRY}/{DEFAULT_SERVER_IMAGE}"
|
|
523
|
-
)
|
|
524
|
-
info(f"Pulling latest server image '{image}'.")
|
|
525
|
-
try:
|
|
526
|
-
docker_client.images.pull(image)
|
|
527
|
-
except Exception as e:
|
|
528
|
-
warning(' ... Getting latest node image failed:')
|
|
529
|
-
warning(f" {e}")
|
|
530
|
-
else:
|
|
531
|
-
info(" ... success!")
|
|
532
|
-
|
|
533
|
-
info("Creating mounts")
|
|
534
|
-
mounts = [
|
|
535
|
-
docker.types.Mount(
|
|
536
|
-
"/mnt/config.yaml", str(ctx.config_file), type="bind"
|
|
537
|
-
),
|
|
538
|
-
docker.types.Mount(
|
|
539
|
-
"/mnt/import.yaml", str(file), type="bind"
|
|
540
|
-
)
|
|
541
|
-
]
|
|
542
|
-
|
|
543
|
-
# FIXME: code duplication with cli_server_start()
|
|
544
|
-
# try to mount database
|
|
545
|
-
uri = ctx.config['uri']
|
|
546
|
-
url = make_url(uri)
|
|
547
|
-
environment_vars = None
|
|
548
|
-
|
|
549
|
-
if mount_src:
|
|
550
|
-
mount_src = os.path.abspath(mount_src)
|
|
551
|
-
mounts.append(docker.types.Mount("/vantage6", mount_src, type="bind"))
|
|
552
|
-
|
|
553
|
-
# If host is None, we're dealing with a file-based DB, like SQLite
|
|
554
|
-
if (url.host is None):
|
|
555
|
-
db_path = url.database
|
|
556
|
-
|
|
557
|
-
if not os.path.isabs(db_path):
|
|
558
|
-
# We're dealing with a relative path here -> make it absolute
|
|
559
|
-
db_path = ctx.data_dir / url.database
|
|
560
|
-
|
|
561
|
-
basename = os.path.basename(db_path)
|
|
562
|
-
dirname = os.path.dirname(db_path)
|
|
563
|
-
os.makedirs(dirname, exist_ok=True)
|
|
564
|
-
|
|
565
|
-
# we're mounting the entire folder that contains the database
|
|
566
|
-
mounts.append(docker.types.Mount(
|
|
567
|
-
"/mnt/database/", dirname, type="bind"
|
|
568
|
-
))
|
|
569
|
-
|
|
570
|
-
environment_vars = {
|
|
571
|
-
"VANTAGE6_DB_URI": f"sqlite:////mnt/database/{basename}"
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
else:
|
|
575
|
-
warning(f"Database could not be transferred, make sure {url.host} "
|
|
576
|
-
"is reachable from the Docker container")
|
|
577
|
-
info("Consider using the docker-compose method to start a server")
|
|
578
|
-
|
|
579
|
-
drop_all_ = "--drop-all" if drop_all else ""
|
|
580
|
-
cmd = (f'vserver-local import -c /mnt/config.yaml {drop_all_} '
|
|
581
|
-
'/mnt/import.yaml')
|
|
582
|
-
|
|
583
|
-
info(cmd)
|
|
584
|
-
|
|
585
|
-
info("Run Docker container")
|
|
586
|
-
container = docker_client.containers.run(
|
|
587
|
-
image,
|
|
588
|
-
command=cmd,
|
|
589
|
-
mounts=mounts,
|
|
590
|
-
detach=True,
|
|
591
|
-
labels={
|
|
592
|
-
f"{APPNAME}-type": "server",
|
|
593
|
-
"name": ctx.config_file_name
|
|
594
|
-
},
|
|
595
|
-
environment=environment_vars,
|
|
596
|
-
auto_remove=not keep,
|
|
597
|
-
tty=True
|
|
598
|
-
)
|
|
599
|
-
logs = container.logs(stream=True, stdout=True)
|
|
600
|
-
Thread(target=_print_log_worker, args=(logs,), daemon=False).start()
|
|
601
|
-
|
|
602
|
-
info(f"Success! container id = {container.id}")
|
|
603
|
-
|
|
604
|
-
if wait:
|
|
605
|
-
container.wait()
|
|
606
|
-
info("Container finished!")
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
#
|
|
610
|
-
# shell
|
|
611
|
-
#
|
|
612
|
-
@cli_server.command(name='shell')
|
|
613
|
-
@click_insert_context
|
|
614
|
-
def cli_server_shell(ctx: ServerContext) -> None:
|
|
615
|
-
"""
|
|
616
|
-
Run an iPython shell within a running server. This can be used to modify
|
|
617
|
-
the database.
|
|
618
|
-
|
|
619
|
-
NOTE: using the shell is no longer recommended as there is no validation on
|
|
620
|
-
the changes that you make. It is better to use the Python client or a
|
|
621
|
-
graphical user interface instead.
|
|
622
|
-
"""
|
|
623
|
-
# will print an error if not
|
|
624
|
-
check_docker_running()
|
|
625
|
-
|
|
626
|
-
docker_client = docker.from_env()
|
|
627
|
-
|
|
628
|
-
running_servers = docker_client.containers.list(
|
|
629
|
-
filters={"label": f"{APPNAME}-type=server"})
|
|
630
|
-
|
|
631
|
-
if ctx.docker_container_name not in [s.name for s in running_servers]:
|
|
632
|
-
error(f"Server {Fore.RED}{ctx.name}{Style.RESET_ALL} is not running?")
|
|
633
|
-
return
|
|
634
|
-
|
|
635
|
-
try:
|
|
636
|
-
subprocess.run(['docker', 'exec', '-it', ctx.docker_container_name,
|
|
637
|
-
'vserver-local', 'shell', '-c', '/mnt/config.yaml'])
|
|
638
|
-
except Exception as e:
|
|
639
|
-
info("Failed to start subprocess...")
|
|
640
|
-
debug_msg(e)
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
#
|
|
644
|
-
# stop
|
|
645
|
-
#
|
|
646
|
-
@cli_server.command(name='stop')
|
|
647
|
-
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
648
|
-
@click.option('--system', 'system_folders', flag_value=True)
|
|
649
|
-
@click.option('--user', 'system_folders', flag_value=False,
|
|
650
|
-
default=DEFAULT_SERVER_SYSTEM_FOLDERS)
|
|
651
|
-
@click.option('--all', 'all_servers', flag_value=True, help="Stop all servers")
|
|
652
|
-
def cli_server_stop(name: str, system_folders: bool, all_servers: bool):
|
|
653
|
-
"""
|
|
654
|
-
Stop one or all running server(s).
|
|
655
|
-
"""
|
|
656
|
-
vserver_stop(name, system_folders, all_servers)
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
def vserver_stop(name: str, system_folders: bool, all_servers: bool) -> None:
|
|
660
|
-
"""
|
|
661
|
-
Stop one or all running server(s).
|
|
662
|
-
|
|
663
|
-
Parameters
|
|
664
|
-
----------
|
|
665
|
-
name : str
|
|
666
|
-
Name of the server to stop
|
|
667
|
-
system_folders : bool
|
|
668
|
-
Wether to use system folders or not
|
|
669
|
-
all_servers : bool
|
|
670
|
-
Wether to stop all servers or not
|
|
671
|
-
"""
|
|
672
|
-
check_docker_running()
|
|
673
|
-
client = docker.from_env()
|
|
674
|
-
|
|
675
|
-
running_servers = client.containers.list(
|
|
676
|
-
filters={"label": f"{APPNAME}-type=server"})
|
|
677
|
-
|
|
678
|
-
if not running_servers:
|
|
679
|
-
warning("No servers are currently running.")
|
|
680
|
-
return
|
|
681
|
-
|
|
682
|
-
running_server_names = [server.name for server in running_servers]
|
|
683
|
-
|
|
684
|
-
if all_servers:
|
|
685
|
-
for container_name in running_server_names:
|
|
686
|
-
_stop_server_containers(client, container_name, system_folders)
|
|
687
|
-
return
|
|
688
|
-
|
|
689
|
-
# make sure we have a configuration name to work with
|
|
690
|
-
if not name:
|
|
691
|
-
container_name = q.select("Select the server you wish to stop:",
|
|
692
|
-
choices=running_server_names).ask()
|
|
693
|
-
else:
|
|
694
|
-
post_fix = "system" if system_folders else "user"
|
|
695
|
-
container_name = f"{APPNAME}-{name}-{post_fix}-server"
|
|
696
|
-
|
|
697
|
-
if container_name not in running_server_names:
|
|
698
|
-
error(f"{Fore.RED}{name}{Style.RESET_ALL} is not running!")
|
|
699
|
-
return
|
|
700
|
-
|
|
701
|
-
_stop_server_containers(client, container_name, system_folders)
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
#
|
|
705
|
-
# attach
|
|
706
|
-
#
|
|
707
|
-
@cli_server.command(name='attach')
|
|
708
|
-
@click.option("-n", "--name", default=None, help="configuration name")
|
|
709
|
-
@click.option('--system', 'system_folders', flag_value=True)
|
|
710
|
-
@click.option('--user', 'system_folders', flag_value=False,
|
|
711
|
-
default=DEFAULT_SERVER_SYSTEM_FOLDERS)
|
|
712
|
-
def cli_server_attach(name: str, system_folders: bool) -> None:
|
|
713
|
-
"""
|
|
714
|
-
Show the server logs in the current console.
|
|
715
|
-
"""
|
|
716
|
-
check_docker_running()
|
|
717
|
-
client = docker.from_env()
|
|
718
|
-
|
|
719
|
-
running_servers = client.containers.list(
|
|
720
|
-
filters={"label": f"{APPNAME}-type=server"})
|
|
721
|
-
running_server_names = [node.name for node in running_servers]
|
|
722
|
-
|
|
723
|
-
if not name:
|
|
724
|
-
name = q.select("Select the server you wish to inspect:",
|
|
725
|
-
choices=running_server_names).ask()
|
|
726
|
-
else:
|
|
727
|
-
post_fix = "system" if system_folders else "user"
|
|
728
|
-
name = f"{APPNAME}-{name}-{post_fix}-server"
|
|
729
|
-
|
|
730
|
-
if name in running_server_names:
|
|
731
|
-
container = client.containers.get(name)
|
|
732
|
-
logs = container.attach(stream=True, logs=True, stdout=True)
|
|
733
|
-
Thread(target=_print_log_worker, args=(logs,), daemon=True).start()
|
|
734
|
-
while True:
|
|
735
|
-
try:
|
|
736
|
-
time.sleep(1)
|
|
737
|
-
except KeyboardInterrupt:
|
|
738
|
-
info("Closing log file. Keyboard Interrupt.")
|
|
739
|
-
info("Note that your server is still running! Shut it down "
|
|
740
|
-
f"with {Fore.RED}vserver stop{Style.RESET_ALL}")
|
|
741
|
-
exit(0)
|
|
742
|
-
else:
|
|
743
|
-
error(f"{Fore.RED}{name}{Style.RESET_ALL} was not running!?")
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
#
|
|
747
|
-
# version
|
|
748
|
-
#
|
|
749
|
-
@cli_server.command(name='version')
|
|
750
|
-
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
751
|
-
@click.option('--system', 'system_folders', flag_value=True)
|
|
752
|
-
@click.option('--user', 'system_folders', flag_value=False,
|
|
753
|
-
default=DEFAULT_SERVER_SYSTEM_FOLDERS)
|
|
754
|
-
def cli_server_version(name: str, system_folders: bool) -> None:
|
|
755
|
-
"""
|
|
756
|
-
Print the version of the vantage6 server.
|
|
757
|
-
"""
|
|
758
|
-
check_docker_running()
|
|
759
|
-
client = docker.from_env()
|
|
760
|
-
|
|
761
|
-
running_servers = client.containers.list(
|
|
762
|
-
filters={"label": f"{APPNAME}-type=server"})
|
|
763
|
-
running_server_names = [server.name for server in running_servers]
|
|
764
|
-
|
|
765
|
-
if not name:
|
|
766
|
-
if not running_server_names:
|
|
767
|
-
error("No servers are running! You can only check the version for "
|
|
768
|
-
"servers that are running")
|
|
769
|
-
exit(1)
|
|
770
|
-
name = q.select("Select the server you wish to inspect:",
|
|
771
|
-
choices=running_server_names).ask()
|
|
772
|
-
else:
|
|
773
|
-
post_fix = "system" if system_folders else "user"
|
|
774
|
-
name = f"{APPNAME}-{name}-{post_fix}"
|
|
775
|
-
|
|
776
|
-
if name in running_server_names:
|
|
777
|
-
container = client.containers.get(name)
|
|
778
|
-
version = container.exec_run(cmd='vserver-local version',
|
|
779
|
-
stdout=True)
|
|
780
|
-
click.echo({"server": version.output.decode('utf-8'),
|
|
781
|
-
"cli": __version__})
|
|
782
|
-
else:
|
|
783
|
-
error(f"Server {name} is not running! Cannot provide version...")
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
#
|
|
787
|
-
# helper functions
|
|
788
|
-
#
|
|
789
|
-
def get_server_context(name: str, system_folders: bool) \
|
|
790
|
-
-> ServerContext:
|
|
791
|
-
"""
|
|
792
|
-
Load the server context from the configuration file.
|
|
793
|
-
|
|
794
|
-
Parameters
|
|
795
|
-
----------
|
|
796
|
-
name : str
|
|
797
|
-
Name of the server to inspect
|
|
798
|
-
system_folders : bool
|
|
799
|
-
Wether to use system folders or if False, the user folders
|
|
800
|
-
|
|
801
|
-
Returns
|
|
802
|
-
-------
|
|
803
|
-
ServerContext
|
|
804
|
-
Server context object
|
|
805
|
-
"""
|
|
806
|
-
if not ServerContext.config_exists(name, system_folders):
|
|
807
|
-
scope = "system" if system_folders else "user"
|
|
808
|
-
error(
|
|
809
|
-
f"Configuration {Fore.RED}{name}{Style.RESET_ALL} does not "
|
|
810
|
-
f"exist in the {Fore.RED}{scope}{Style.RESET_ALL} folders!"
|
|
811
|
-
)
|
|
812
|
-
exit(1)
|
|
813
|
-
|
|
814
|
-
# We do not want to log this here, we do this in the container and not on
|
|
815
|
-
# the host. We only want CLI logging here.
|
|
816
|
-
ServerContext.LOGGING_ENABLED = False
|
|
817
|
-
|
|
818
|
-
# create server context, and initialize db
|
|
819
|
-
ctx = ServerContext(name, system_folders=system_folders)
|
|
820
|
-
|
|
821
|
-
return ctx
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
def _start_rabbitmq(ctx: ServerContext, rabbitmq_image: str,
|
|
825
|
-
network_mgr: NetworkManager) -> None:
|
|
826
|
-
"""
|
|
827
|
-
Start the RabbitMQ container if it is not already running.
|
|
828
|
-
|
|
829
|
-
Parameters
|
|
830
|
-
----------
|
|
831
|
-
ctx : ServerContext
|
|
832
|
-
Server context object
|
|
833
|
-
rabbitmq_image : str
|
|
834
|
-
RabbitMQ image to use
|
|
835
|
-
network_mgr : NetworkManager
|
|
836
|
-
Network manager object
|
|
837
|
-
"""
|
|
838
|
-
rabbit_uri = ctx.config['rabbitmq'].get('uri')
|
|
839
|
-
if not rabbit_uri:
|
|
840
|
-
error("No RabbitMQ URI found in the configuration file! Please add"
|
|
841
|
-
"a 'uri' key to the 'rabbitmq' section of the configuration.")
|
|
842
|
-
exit(1)
|
|
843
|
-
# kick off RabbitMQ container
|
|
844
|
-
rabbit_mgr = RabbitMQManager(
|
|
845
|
-
ctx=ctx, network_mgr=network_mgr, image=rabbitmq_image)
|
|
846
|
-
rabbit_mgr.start()
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
def _stop_server_containers(client: DockerClient, container_name: str,
|
|
850
|
-
system_folders: bool) -> None:
|
|
851
|
-
"""
|
|
852
|
-
Given a server's name, kill its docker container and related (RabbitMQ)
|
|
853
|
-
containers.
|
|
854
|
-
|
|
855
|
-
Parameters
|
|
856
|
-
----------
|
|
857
|
-
client : DockerClient
|
|
858
|
-
Docker client
|
|
859
|
-
container_name : str
|
|
860
|
-
Name of the server to stop
|
|
861
|
-
system_folders : bool
|
|
862
|
-
Wether to use system folders or not
|
|
863
|
-
"""
|
|
864
|
-
# kill the server
|
|
865
|
-
remove_container_if_exists(client, name=container_name)
|
|
866
|
-
info(f"Stopped the {Fore.GREEN}{container_name}{Style.RESET_ALL} server.")
|
|
867
|
-
|
|
868
|
-
# find the configuration name from the docker container name
|
|
869
|
-
# server name is formatted as f"{APPNAME}-{self.name}-{self.scope}-server"
|
|
870
|
-
scope = "system" if system_folders else "user"
|
|
871
|
-
config_name = get_server_config_name(container_name, scope)
|
|
872
|
-
|
|
873
|
-
ctx = get_server_context(config_name, system_folders)
|
|
874
|
-
|
|
875
|
-
# kill the UI container (if it exists)
|
|
876
|
-
_stop_ui(client, ctx)
|
|
877
|
-
|
|
878
|
-
# delete the server network
|
|
879
|
-
network_name = f"{APPNAME}-{ctx.name}-{ctx.scope}-network"
|
|
880
|
-
network = get_network(client, name=network_name)
|
|
881
|
-
delete_network(network, kill_containers=False)
|
|
882
|
-
|
|
883
|
-
# kill RabbitMQ if it exists and no other servers are using to it (i.e. it
|
|
884
|
-
# is not in other docker networks with other containers)
|
|
885
|
-
rabbit_uri = ctx.config.get('rabbitmq', {}).get('uri')
|
|
886
|
-
if rabbit_uri:
|
|
887
|
-
rabbit_container_name = split_rabbitmq_uri(
|
|
888
|
-
rabbit_uri=rabbit_uri)['host']
|
|
889
|
-
rabbit_container = get_container(client, name=rabbit_container_name)
|
|
890
|
-
if rabbit_container and \
|
|
891
|
-
get_num_nonempty_networks(rabbit_container) == 0:
|
|
892
|
-
remove_container(rabbit_container, kill=True)
|
|
893
|
-
info(f"Stopped the {Fore.GREEN}{rabbit_container_name}"
|
|
894
|
-
f"{Style.RESET_ALL} container.")
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
def _print_log_worker(logs_stream: Iterable[bytes]) -> None:
|
|
898
|
-
"""
|
|
899
|
-
Print the logs from the docker container to the terminal.
|
|
900
|
-
|
|
901
|
-
Parameters
|
|
902
|
-
----------
|
|
903
|
-
logs_stream : Iterable[bytes]
|
|
904
|
-
Output of the `container.attach(.)` method
|
|
905
|
-
"""
|
|
906
|
-
for log in logs_stream:
|
|
907
|
-
print(log.decode(STRING_ENCODING), end="")
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
def vserver_remove(ctx: ServerContext, name: str, system_folders: bool,
|
|
911
|
-
force: bool) -> None:
|
|
912
|
-
"""
|
|
913
|
-
Function to remove a server.
|
|
914
|
-
|
|
915
|
-
Parameters
|
|
916
|
-
----------
|
|
917
|
-
ctx : ServerContext
|
|
918
|
-
Server context object
|
|
919
|
-
name : str
|
|
920
|
-
Name of the server to remove
|
|
921
|
-
system_folders : bool
|
|
922
|
-
Whether to use system folders or not
|
|
923
|
-
force : bool
|
|
924
|
-
Whether to ask for confirmation before removing or not
|
|
925
|
-
"""
|
|
926
|
-
check_docker_running()
|
|
927
|
-
|
|
928
|
-
# first stop server
|
|
929
|
-
vserver_stop(name, system_folders, False)
|
|
930
|
-
|
|
931
|
-
if not force:
|
|
932
|
-
if not q.confirm(
|
|
933
|
-
"This server will be deleted permanently including its "
|
|
934
|
-
"configuration. Are you sure?", default=False
|
|
935
|
-
).ask():
|
|
936
|
-
info("Server will not be deleted")
|
|
937
|
-
exit(0)
|
|
938
|
-
|
|
939
|
-
# now remove the folders...
|
|
940
|
-
info(f"Removing configuration file {ctx.config_file}")
|
|
941
|
-
remove_file(ctx.config_file, 'configuration')
|
|
942
|
-
|
|
943
|
-
info(f"Removing log file {ctx.log_file}")
|
|
944
|
-
for handler in itertools.chain(ctx.log.handlers, ctx.log.root.handlers):
|
|
945
|
-
handler.close()
|
|
946
|
-
remove_file(ctx.log_file, 'log')
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
def _start_ui(client: DockerClient, ctx: ServerContext, ui_port: int) -> None:
|
|
950
|
-
"""
|
|
951
|
-
Start the UI container.
|
|
952
|
-
|
|
953
|
-
Parameters
|
|
954
|
-
----------
|
|
955
|
-
client : DockerClient
|
|
956
|
-
Docker client
|
|
957
|
-
ctx : ServerContext
|
|
958
|
-
Server context object
|
|
959
|
-
ui_port : int
|
|
960
|
-
Port to expose the UI on
|
|
961
|
-
"""
|
|
962
|
-
# if no port is specified, check if config contains a port
|
|
963
|
-
ui_config = ctx.config.get('ui')
|
|
964
|
-
if ui_config and not ui_port:
|
|
965
|
-
ui_port = ui_config.get('port')
|
|
966
|
-
|
|
967
|
-
# check if the port is valid
|
|
968
|
-
# TODO make function to check if port is valid, and use in more places
|
|
969
|
-
if not isinstance(ui_port, int) or not 0 < ui_port < 65536:
|
|
970
|
-
warning(f"UI port '{ui_port}' is not valid! Using default port "
|
|
971
|
-
f"{DEFAULT_UI_PORT}")
|
|
972
|
-
ui_port = DEFAULT_UI_PORT
|
|
973
|
-
|
|
974
|
-
# find image to use
|
|
975
|
-
custom_images: dict = ctx.config.get('images')
|
|
976
|
-
image = None
|
|
977
|
-
if custom_images:
|
|
978
|
-
image = custom_images.get('ui')
|
|
979
|
-
if not image:
|
|
980
|
-
image = f"{DEFAULT_DOCKER_REGISTRY}/{DEFAULT_UI_IMAGE}"
|
|
981
|
-
|
|
982
|
-
info(f"Pulling latest UI image '{image}'.")
|
|
983
|
-
try:
|
|
984
|
-
pull_if_newer(docker.from_env(), image)
|
|
985
|
-
# docker_client.images.pull(image)
|
|
986
|
-
except Exception as e:
|
|
987
|
-
warning(' ... Getting latest node image failed:')
|
|
988
|
-
warning(f" {e}")
|
|
989
|
-
else:
|
|
990
|
-
info(" ... success!")
|
|
991
|
-
|
|
992
|
-
# set environment variables
|
|
993
|
-
env_vars = {
|
|
994
|
-
"SERVER_URL": f"http://localhost:{ctx.config.get('port')}",
|
|
995
|
-
"API_PATH": ctx.config.get("api_path"),
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
# stop the UI container if it is already running
|
|
999
|
-
_stop_ui(client, ctx)
|
|
1000
|
-
|
|
1001
|
-
info(f'Starting User Interface at port {ui_port}')
|
|
1002
|
-
ui_container_name = f"{APPNAME}-{ctx.name}-{ctx.scope}-ui"
|
|
1003
|
-
client.containers.run(
|
|
1004
|
-
image,
|
|
1005
|
-
detach=True,
|
|
1006
|
-
labels={
|
|
1007
|
-
f"{APPNAME}-type": "ui",
|
|
1008
|
-
"name": ctx.config_file_name
|
|
1009
|
-
},
|
|
1010
|
-
ports={"80/tcp": (ctx.config.get('ip'), ui_port)},
|
|
1011
|
-
name=ui_container_name,
|
|
1012
|
-
environment=env_vars,
|
|
1013
|
-
tty=True,
|
|
1014
|
-
)
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
def _stop_ui(client: DockerClient, ctx: ServerContext) -> None:
|
|
1018
|
-
"""
|
|
1019
|
-
Check if the UI container is running, and if so, stop and remove it.
|
|
1020
|
-
|
|
1021
|
-
Parameters
|
|
1022
|
-
----------
|
|
1023
|
-
client : DockerClient
|
|
1024
|
-
Docker client
|
|
1025
|
-
ctx : ServerContext
|
|
1026
|
-
Server context object
|
|
1027
|
-
"""
|
|
1028
|
-
ui_container_name = f"{APPNAME}-{ctx.name}-{ctx.scope}-ui"
|
|
1029
|
-
ui_container = get_container(client, name=ui_container_name)
|
|
1030
|
-
if ui_container:
|
|
1031
|
-
remove_container(ui_container, kill=True)
|
|
1032
|
-
info(f"Stopped the {Fore.GREEN}{ui_container_name}"
|
|
1033
|
-
f"{Style.RESET_ALL} User Interface container.")
|