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/node.py
DELETED
|
@@ -1,1097 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
The node module contains the CLI commands for the node manager. The following
|
|
3
|
-
commands are available:
|
|
4
|
-
|
|
5
|
-
* vnode new
|
|
6
|
-
* vnode list
|
|
7
|
-
* vnode files
|
|
8
|
-
* vnode start
|
|
9
|
-
* vnode stop
|
|
10
|
-
* vnode attach
|
|
11
|
-
* vnode clean
|
|
12
|
-
* vnode remove
|
|
13
|
-
* vnode version
|
|
14
|
-
* vnode create-private-key
|
|
15
|
-
"""
|
|
16
|
-
import click
|
|
17
|
-
import sys
|
|
18
|
-
import questionary as q
|
|
19
|
-
import docker
|
|
20
|
-
import time
|
|
21
|
-
import os.path
|
|
22
|
-
import itertools
|
|
23
|
-
|
|
24
|
-
from typing import Iterable
|
|
25
|
-
from pathlib import Path
|
|
26
|
-
from threading import Thread
|
|
27
|
-
from colorama import Fore, Style
|
|
28
|
-
from shutil import rmtree
|
|
29
|
-
|
|
30
|
-
from vantage6.common import (
|
|
31
|
-
warning, error, info, debug,
|
|
32
|
-
bytes_to_base64s, check_config_writeable,
|
|
33
|
-
get_database_config
|
|
34
|
-
)
|
|
35
|
-
from vantage6.common.globals import (
|
|
36
|
-
STRING_ENCODING,
|
|
37
|
-
APPNAME,
|
|
38
|
-
DEFAULT_DOCKER_REGISTRY,
|
|
39
|
-
DEFAULT_NODE_IMAGE,
|
|
40
|
-
DEFAULT_NODE_IMAGE_WO_TAG,
|
|
41
|
-
)
|
|
42
|
-
from vantage6.common.globals import VPN_CONFIG_FILE
|
|
43
|
-
from vantage6.common.docker.addons import (
|
|
44
|
-
pull_if_newer,
|
|
45
|
-
remove_container_if_exists,
|
|
46
|
-
check_docker_running
|
|
47
|
-
)
|
|
48
|
-
from vantage6.common.encryption import RSACryptor
|
|
49
|
-
from vantage6.client import UserClient
|
|
50
|
-
|
|
51
|
-
from vantage6.cli.context import NodeContext
|
|
52
|
-
from vantage6.cli.globals import (
|
|
53
|
-
DEFAULT_NODE_SYSTEM_FOLDERS as N_FOL
|
|
54
|
-
)
|
|
55
|
-
from vantage6.cli.configuration_wizard import (
|
|
56
|
-
configuration_wizard,
|
|
57
|
-
select_configuration_questionaire,
|
|
58
|
-
NodeConfigurationManager
|
|
59
|
-
)
|
|
60
|
-
from vantage6.cli.utils import (
|
|
61
|
-
check_config_name_allowed, check_if_docker_daemon_is_running,
|
|
62
|
-
prompt_config_name, remove_file
|
|
63
|
-
)
|
|
64
|
-
from vantage6.cli import __version__
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
@click.group(name="node")
|
|
68
|
-
def cli_node() -> None:
|
|
69
|
-
"""
|
|
70
|
-
The `vnode` commands allow you to manage your vantage6 node instances.
|
|
71
|
-
"""
|
|
72
|
-
pass
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
#
|
|
76
|
-
# list
|
|
77
|
-
#
|
|
78
|
-
@cli_node.command(name="list")
|
|
79
|
-
def cli_node_list() -> None:
|
|
80
|
-
"""
|
|
81
|
-
Lists all node configurations.
|
|
82
|
-
|
|
83
|
-
Note that this command cannot find node configuration files in custom
|
|
84
|
-
directories.
|
|
85
|
-
"""
|
|
86
|
-
|
|
87
|
-
check_docker_running()
|
|
88
|
-
client = docker.from_env()
|
|
89
|
-
|
|
90
|
-
running_node_names = _find_running_node_names(client)
|
|
91
|
-
|
|
92
|
-
header = \
|
|
93
|
-
"\nName"+(21*" ") + \
|
|
94
|
-
"Status"+(10*" ") + \
|
|
95
|
-
"System/User"
|
|
96
|
-
|
|
97
|
-
click.echo(header)
|
|
98
|
-
click.echo("-"*len(header))
|
|
99
|
-
|
|
100
|
-
running = Fore.GREEN + "Running" + Style.RESET_ALL
|
|
101
|
-
stopped = Fore.RED + "Not running" + Style.RESET_ALL
|
|
102
|
-
|
|
103
|
-
# system folders
|
|
104
|
-
configs, f1 = NodeContext.available_configurations(
|
|
105
|
-
system_folders=True)
|
|
106
|
-
for config in configs:
|
|
107
|
-
status = running if f"{APPNAME}-{config.name}-system" in \
|
|
108
|
-
running_node_names else stopped
|
|
109
|
-
click.echo(
|
|
110
|
-
f"{config.name:25}"
|
|
111
|
-
f"{status:25}System "
|
|
112
|
-
)
|
|
113
|
-
|
|
114
|
-
# user folders
|
|
115
|
-
configs, f2 = NodeContext.available_configurations(
|
|
116
|
-
system_folders=False)
|
|
117
|
-
for config in configs:
|
|
118
|
-
status = running if f"{APPNAME}-{config.name}-user" in \
|
|
119
|
-
running_node_names else stopped
|
|
120
|
-
click.echo(
|
|
121
|
-
f"{config.name:25}"
|
|
122
|
-
f"{status:25}User "
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
click.echo("-"*53)
|
|
126
|
-
if len(f1)+len(f2):
|
|
127
|
-
warning(
|
|
128
|
-
f"{Fore.RED}Failed imports: {len(f1)+len(f2)}{Style.RESET_ALL}")
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
#
|
|
132
|
-
# new
|
|
133
|
-
#
|
|
134
|
-
@cli_node.command(name="new")
|
|
135
|
-
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
136
|
-
@click.option('--system', 'system_folders', flag_value=True,
|
|
137
|
-
help="Store this configuration in the system folders")
|
|
138
|
-
@click.option('--user', 'system_folders', flag_value=False, default=N_FOL,
|
|
139
|
-
help="Store this configuration in the user folders. This is the "
|
|
140
|
-
"default")
|
|
141
|
-
def cli_node_new_configuration(name: str, system_folders: bool) -> None:
|
|
142
|
-
"""
|
|
143
|
-
Create a new node configuration.
|
|
144
|
-
|
|
145
|
-
Checks if the configuration already exists. If this is not the case
|
|
146
|
-
a questionnaire is invoked to create a new configuration file.
|
|
147
|
-
"""
|
|
148
|
-
name = prompt_config_name(name)
|
|
149
|
-
# check if config name is allowed docker name
|
|
150
|
-
check_config_name_allowed(name)
|
|
151
|
-
|
|
152
|
-
# check that this config does not exist
|
|
153
|
-
if NodeContext.config_exists(name, system_folders):
|
|
154
|
-
error(f"Configuration {name} already exists!")
|
|
155
|
-
exit(1)
|
|
156
|
-
|
|
157
|
-
# Check that we can write in this folder
|
|
158
|
-
if not check_config_writeable(system_folders):
|
|
159
|
-
error("Cannot write configuration file. Exiting...")
|
|
160
|
-
exit(1)
|
|
161
|
-
|
|
162
|
-
# create config in ctx location
|
|
163
|
-
flag = "--system" if system_folders else ""
|
|
164
|
-
cfg_file = configuration_wizard("node", name, system_folders)
|
|
165
|
-
info(f"New configuration created: {Fore.GREEN}{cfg_file}{Style.RESET_ALL}")
|
|
166
|
-
info(f"You can start the node by running "
|
|
167
|
-
f"{Fore.GREEN}vnode start {flag}{Style.RESET_ALL}")
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
#
|
|
171
|
-
# files
|
|
172
|
-
#
|
|
173
|
-
@cli_node.command(name="files")
|
|
174
|
-
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
175
|
-
@click.option('--system', 'system_folders', flag_value=True,
|
|
176
|
-
help="Search for the configuration in the system folders")
|
|
177
|
-
@click.option('--user', 'system_folders', flag_value=False, default=N_FOL,
|
|
178
|
-
help="Search for the configuration in the user folders. This is "
|
|
179
|
-
"the default")
|
|
180
|
-
def cli_node_files(name: str, system_folders: bool) -> None:
|
|
181
|
-
"""
|
|
182
|
-
Prints the location of important node files.
|
|
183
|
-
|
|
184
|
-
If the specified configuration cannot be found, it exits. Otherwise
|
|
185
|
-
it returns the absolute path to the output.
|
|
186
|
-
"""
|
|
187
|
-
name = _select_node(name, system_folders)
|
|
188
|
-
|
|
189
|
-
# create node context
|
|
190
|
-
ctx = NodeContext(name, system_folders=system_folders)
|
|
191
|
-
|
|
192
|
-
# return path of the configuration
|
|
193
|
-
info(f"Configuration file = {ctx.config_file}")
|
|
194
|
-
info(f"Log file = {ctx.log_file}")
|
|
195
|
-
info(f"data folders = {ctx.data_dir}")
|
|
196
|
-
info("Database labels and files")
|
|
197
|
-
for db in ctx.databases:
|
|
198
|
-
info(f" - {db['label']:15} = {db['uri']} (type: {db['type']})")
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
#
|
|
202
|
-
# start
|
|
203
|
-
#
|
|
204
|
-
@cli_node.command(name='start')
|
|
205
|
-
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
206
|
-
@click.option("-c", "--config", default=None,
|
|
207
|
-
help='Absolute path to configuration-file; overrides NAME')
|
|
208
|
-
@click.option('--system', 'system_folders', flag_value=True,
|
|
209
|
-
help="Search for the configuration in the system folders")
|
|
210
|
-
@click.option('--user', 'system_folders', flag_value=False, default=N_FOL,
|
|
211
|
-
help="Search for the configuration in the user folders. This is "
|
|
212
|
-
"the default")
|
|
213
|
-
@click.option('-i', '--image', default=None, help="Node Docker image to use")
|
|
214
|
-
@click.option('--keep/--auto-remove', default=False,
|
|
215
|
-
help="Keep node container after finishing. Useful for debugging")
|
|
216
|
-
@click.option('--force-db-mount', is_flag=True,
|
|
217
|
-
help="Always mount node databases; skip the check if they are "
|
|
218
|
-
"existing files.")
|
|
219
|
-
@click.option('--attach/--detach', default=False,
|
|
220
|
-
help="Show node logs on the current console after starting the "
|
|
221
|
-
"node")
|
|
222
|
-
@click.option('--mount-src', default='',
|
|
223
|
-
help="Override vantage6 source code in container with the source"
|
|
224
|
-
" code in this path")
|
|
225
|
-
def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
226
|
-
keep: bool, mount_src: str, attach: bool,
|
|
227
|
-
force_db_mount: bool) -> None:
|
|
228
|
-
"""
|
|
229
|
-
Start the node.
|
|
230
|
-
"""
|
|
231
|
-
vnode_start(name, config, system_folders, image, keep, mount_src, attach,
|
|
232
|
-
force_db_mount)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
def vnode_start(name: str, config: str, system_folders: bool,
|
|
236
|
-
image: str, keep: bool, mount_src: str, attach: bool,
|
|
237
|
-
force_db_mount: bool) -> None:
|
|
238
|
-
"""
|
|
239
|
-
Start the node instance inside a Docker container.
|
|
240
|
-
|
|
241
|
-
Parameters
|
|
242
|
-
----------
|
|
243
|
-
name : str
|
|
244
|
-
Name of the configuration file.
|
|
245
|
-
config : str
|
|
246
|
-
Absolute path to configuration-file; overrides NAME
|
|
247
|
-
system_folders : bool
|
|
248
|
-
Is this configuration stored in the system or in the user folders.
|
|
249
|
-
image : str
|
|
250
|
-
Node Docker image to use.
|
|
251
|
-
keep : bool
|
|
252
|
-
Keep container when finished or in the event of a crash. This is useful
|
|
253
|
-
for debugging.
|
|
254
|
-
mount_src : str
|
|
255
|
-
Mount vantage6 package source that replaces the source inside the
|
|
256
|
-
container. This is useful for debugging.
|
|
257
|
-
attach : bool
|
|
258
|
-
Attach node logs to the console after start.
|
|
259
|
-
force_db_mount : bool
|
|
260
|
-
Skip the check of the existence of the DB (always try to mount).
|
|
261
|
-
"""
|
|
262
|
-
check_docker_running()
|
|
263
|
-
info("Starting node...")
|
|
264
|
-
info("Finding Docker daemon")
|
|
265
|
-
docker_client = docker.from_env()
|
|
266
|
-
NodeContext.LOGGING_ENABLED = False
|
|
267
|
-
if config:
|
|
268
|
-
name = Path(config).stem
|
|
269
|
-
ctx = NodeContext(name, system_folders, config)
|
|
270
|
-
|
|
271
|
-
else:
|
|
272
|
-
# in case no name is supplied, ask the user to select one
|
|
273
|
-
if not name:
|
|
274
|
-
name = select_configuration_questionaire("node", system_folders)
|
|
275
|
-
|
|
276
|
-
# check that config exists, if not a questionaire will be invoked
|
|
277
|
-
if not NodeContext.config_exists(name, system_folders):
|
|
278
|
-
warning(f"Configuration {Fore.RED}{name}{Style.RESET_ALL} does not"
|
|
279
|
-
" exist.")
|
|
280
|
-
|
|
281
|
-
if q.confirm("Create this configuration now?").ask():
|
|
282
|
-
configuration_wizard("node", name, system_folders)
|
|
283
|
-
|
|
284
|
-
else:
|
|
285
|
-
error("Config file couldn't be loaded")
|
|
286
|
-
sys.exit(0)
|
|
287
|
-
|
|
288
|
-
ctx = NodeContext(name, system_folders)
|
|
289
|
-
|
|
290
|
-
# check if config name is allowed docker name, else exit
|
|
291
|
-
check_config_name_allowed(ctx.name)
|
|
292
|
-
|
|
293
|
-
# check that this node is not already running
|
|
294
|
-
running_nodes = docker_client.containers.list(
|
|
295
|
-
filters={"label": f"{APPNAME}-type=node"}
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
suffix = "system" if system_folders else "user"
|
|
299
|
-
for node in running_nodes:
|
|
300
|
-
if node.name == f"{APPNAME}-{name}-{suffix}":
|
|
301
|
-
error(f"Node {Fore.RED}{name}{Style.RESET_ALL} is already running")
|
|
302
|
-
exit(1)
|
|
303
|
-
|
|
304
|
-
# make sure the (host)-task and -log dir exists
|
|
305
|
-
info("Checking that data and log dirs exist")
|
|
306
|
-
ctx.data_dir.mkdir(parents=True, exist_ok=True)
|
|
307
|
-
ctx.log_dir.mkdir(parents=True, exist_ok=True)
|
|
308
|
-
|
|
309
|
-
# Determine image-name. First we check if the option --image has been used.
|
|
310
|
-
# Then we check if the image has been specified in the config file, and
|
|
311
|
-
# finally we use the default settings from the package.
|
|
312
|
-
if not image:
|
|
313
|
-
custom_images: dict = ctx.config.get('images')
|
|
314
|
-
if custom_images:
|
|
315
|
-
image = custom_images.get("node")
|
|
316
|
-
|
|
317
|
-
else:
|
|
318
|
-
# if no custom image is specified, find the server version and use
|
|
319
|
-
# the latest images from that minor version
|
|
320
|
-
client = _create_client(ctx)
|
|
321
|
-
major_minor = None
|
|
322
|
-
try:
|
|
323
|
-
# try to get server version, skip if can't get a connection
|
|
324
|
-
version = client.util.get_server_version(
|
|
325
|
-
attempts_on_timeout=3
|
|
326
|
-
)['version']
|
|
327
|
-
major_minor = '.'.join(version.split('.')[:2])
|
|
328
|
-
image = (f"{DEFAULT_DOCKER_REGISTRY}/"
|
|
329
|
-
f"{DEFAULT_NODE_IMAGE_WO_TAG}"
|
|
330
|
-
f":{major_minor}")
|
|
331
|
-
except Exception:
|
|
332
|
-
warning("Could not determine server version. Using default "
|
|
333
|
-
"node image")
|
|
334
|
-
pass # simply use the default image
|
|
335
|
-
|
|
336
|
-
if major_minor and not __version__.startswith(major_minor):
|
|
337
|
-
warning(
|
|
338
|
-
"Version mismatch between CLI and server/node. CLI is "
|
|
339
|
-
f"running on version {__version__}, while node and server "
|
|
340
|
-
f"are on version {major_minor}. This might cause "
|
|
341
|
-
f"unexpected issues; changing to {major_minor}.<latest> "
|
|
342
|
-
"is recommended."
|
|
343
|
-
)
|
|
344
|
-
|
|
345
|
-
# fail safe, in case no custom image is specified and we can't get the
|
|
346
|
-
# server version
|
|
347
|
-
if not image:
|
|
348
|
-
image = f"{DEFAULT_DOCKER_REGISTRY}/{DEFAULT_NODE_IMAGE}"
|
|
349
|
-
|
|
350
|
-
info(f"Pulling latest node image '{image}'")
|
|
351
|
-
try:
|
|
352
|
-
# docker_client.images.pull(image)
|
|
353
|
-
pull_if_newer(docker.from_env(), image)
|
|
354
|
-
|
|
355
|
-
except Exception as e:
|
|
356
|
-
warning(' ... Getting latest node image failed:')
|
|
357
|
-
warning(f" {e}")
|
|
358
|
-
else:
|
|
359
|
-
info(" ... success!")
|
|
360
|
-
|
|
361
|
-
info("Creating Docker data volume")
|
|
362
|
-
|
|
363
|
-
data_volume = docker_client.volumes.create(ctx.docker_volume_name)
|
|
364
|
-
vpn_volume = docker_client.volumes.create(ctx.docker_vpn_volume_name)
|
|
365
|
-
ssh_volume = docker_client.volumes.create(ctx.docker_ssh_volume_name)
|
|
366
|
-
squid_volume = docker_client.volumes.create(ctx.docker_squid_volume_name)
|
|
367
|
-
|
|
368
|
-
info("Creating file & folder mounts")
|
|
369
|
-
# FIXME: should obtain mount points from DockerNodeContext
|
|
370
|
-
mounts = [
|
|
371
|
-
# (target, source)
|
|
372
|
-
("/mnt/log", str(ctx.log_dir)),
|
|
373
|
-
("/mnt/data", data_volume.name),
|
|
374
|
-
("/mnt/vpn", vpn_volume.name),
|
|
375
|
-
("/mnt/ssh", ssh_volume.name),
|
|
376
|
-
("/mnt/squid", squid_volume.name),
|
|
377
|
-
("/mnt/config", str(ctx.config_dir)),
|
|
378
|
-
("/var/run/docker.sock", "/var/run/docker.sock"),
|
|
379
|
-
]
|
|
380
|
-
|
|
381
|
-
if mount_src:
|
|
382
|
-
# If mount_src is a relative path, docker will consider it a volume.
|
|
383
|
-
mount_src = os.path.abspath(mount_src)
|
|
384
|
-
mounts.append(('/vantage6', mount_src))
|
|
385
|
-
|
|
386
|
-
# FIXME: Code duplication: Node.__init__() (vantage6/node/__init__.py)
|
|
387
|
-
# uses a lot of the same logic. Suggest moving this to
|
|
388
|
-
# ctx.get_private_key()
|
|
389
|
-
filename = ctx.config.get("encryption", {}).get("private_key")
|
|
390
|
-
# filename may be set to an empty string
|
|
391
|
-
if not filename:
|
|
392
|
-
filename = 'private_key.pem'
|
|
393
|
-
|
|
394
|
-
# Location may be overridden by the environment
|
|
395
|
-
filename = os.environ.get('PRIVATE_KEY', filename)
|
|
396
|
-
|
|
397
|
-
# If ctx.get_data_file() receives an absolute path, it is returned as-is
|
|
398
|
-
fullpath = Path(ctx.get_data_file(filename))
|
|
399
|
-
if fullpath:
|
|
400
|
-
if Path(fullpath).exists():
|
|
401
|
-
mounts.append(("/mnt/private_key.pem", str(fullpath)))
|
|
402
|
-
else:
|
|
403
|
-
warning(f"private key file provided {fullpath}, "
|
|
404
|
-
"but does not exists")
|
|
405
|
-
|
|
406
|
-
# Mount private keys for ssh tunnels
|
|
407
|
-
ssh_tunnels = ctx.config.get("ssh-tunnels", [])
|
|
408
|
-
for ssh_tunnel in ssh_tunnels:
|
|
409
|
-
hostname = ssh_tunnel.get("hostname")
|
|
410
|
-
key_path = ssh_tunnel.get("ssh", {}).get("identity", {}).get("key")
|
|
411
|
-
if not key_path:
|
|
412
|
-
error(f"SSH tunnel identity {Fore.RED}{hostname}{Style.RESET_ALL} "
|
|
413
|
-
"key not provided. Continuing to start without this tunnel.")
|
|
414
|
-
key_path = Path(key_path)
|
|
415
|
-
if not key_path.exists():
|
|
416
|
-
error(f"SSH tunnel identity {Fore.RED}{hostname}{Style.RESET_ALL} "
|
|
417
|
-
"key does not exist. Continuing to start without this "
|
|
418
|
-
"tunnel.")
|
|
419
|
-
|
|
420
|
-
info(f" Mounting private key for {hostname} at {key_path}")
|
|
421
|
-
|
|
422
|
-
# we remove the .tmp in the container, this is because the file is
|
|
423
|
-
# mounted in a volume mount point. Somehow the file is than empty in
|
|
424
|
-
# the volume but not for the node instance. By removing the .tmp we
|
|
425
|
-
# make sure that the file is not empty in the volume.
|
|
426
|
-
mounts.append((f"/mnt/ssh/{hostname}.pem.tmp", str(key_path)))
|
|
427
|
-
|
|
428
|
-
env = {
|
|
429
|
-
"DATA_VOLUME_NAME": data_volume.name,
|
|
430
|
-
"VPN_VOLUME_NAME": vpn_volume.name,
|
|
431
|
-
"PRIVATE_KEY": "/mnt/private_key.pem"
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
# only mount the DB if it is a file
|
|
435
|
-
info("Setting up databases")
|
|
436
|
-
db_labels = [db['label'] for db in ctx.databases]
|
|
437
|
-
for label in db_labels:
|
|
438
|
-
|
|
439
|
-
db_config = get_database_config(ctx.databases, label)
|
|
440
|
-
uri = db_config['uri']
|
|
441
|
-
db_type = db_config['type']
|
|
442
|
-
|
|
443
|
-
info(f" Processing {Fore.GREEN}{db_type}{Style.RESET_ALL} database "
|
|
444
|
-
f"{Fore.GREEN}{label}:{uri}{Style.RESET_ALL}")
|
|
445
|
-
label_capitals = label.upper()
|
|
446
|
-
|
|
447
|
-
try:
|
|
448
|
-
file_based = Path(uri).exists()
|
|
449
|
-
except Exception:
|
|
450
|
-
# If the database uri cannot be parsed, it is definitely not a
|
|
451
|
-
# file. In case of http servers or sql servers, checking the path
|
|
452
|
-
# of the the uri will lead to an OS-dependent error, which is why
|
|
453
|
-
# we catch all exceptions here.
|
|
454
|
-
file_based = False
|
|
455
|
-
|
|
456
|
-
if not file_based and not force_db_mount:
|
|
457
|
-
debug(' - non file-based database added')
|
|
458
|
-
env[f'{label_capitals}_DATABASE_URI'] = uri
|
|
459
|
-
else:
|
|
460
|
-
debug(' - file-based database added')
|
|
461
|
-
suffix = Path(uri).suffix
|
|
462
|
-
env[f'{label_capitals}_DATABASE_URI'] = f'{label}{suffix}'
|
|
463
|
-
mounts.append((f'/mnt/{label}{suffix}', str(uri)))
|
|
464
|
-
|
|
465
|
-
system_folders_option = "--system" if system_folders else "--user"
|
|
466
|
-
cmd = f'vnode-local start -c /mnt/config/{name}.yaml -n {name} '\
|
|
467
|
-
f' --dockerized {system_folders_option}'
|
|
468
|
-
|
|
469
|
-
info("Running Docker container")
|
|
470
|
-
volumes = []
|
|
471
|
-
for mount in mounts:
|
|
472
|
-
volumes.append(f'{mount[1]}:{mount[0]}')
|
|
473
|
-
|
|
474
|
-
remove_container_if_exists(
|
|
475
|
-
docker_client=docker_client, name=ctx.docker_container_name
|
|
476
|
-
)
|
|
477
|
-
|
|
478
|
-
container = docker_client.containers.run(
|
|
479
|
-
image,
|
|
480
|
-
command=cmd,
|
|
481
|
-
volumes=volumes,
|
|
482
|
-
detach=True,
|
|
483
|
-
labels={
|
|
484
|
-
f"{APPNAME}-type": "node",
|
|
485
|
-
"system": str(system_folders),
|
|
486
|
-
"name": ctx.config_file_name
|
|
487
|
-
},
|
|
488
|
-
environment=env,
|
|
489
|
-
name=ctx.docker_container_name,
|
|
490
|
-
auto_remove=not keep,
|
|
491
|
-
tty=True
|
|
492
|
-
)
|
|
493
|
-
|
|
494
|
-
info(f"Success! container id = {container}")
|
|
495
|
-
|
|
496
|
-
if attach:
|
|
497
|
-
logs = container.attach(stream=True, logs=True)
|
|
498
|
-
Thread(target=_print_log_worker, args=(logs,), daemon=True).start()
|
|
499
|
-
while True:
|
|
500
|
-
try:
|
|
501
|
-
time.sleep(1)
|
|
502
|
-
except KeyboardInterrupt:
|
|
503
|
-
info("Closing log file. Keyboard Interrupt.")
|
|
504
|
-
info("Note that your node is still running! Shut it down with "
|
|
505
|
-
f"'{Fore.RED}vnode stop{Style.RESET_ALL}'")
|
|
506
|
-
exit(0)
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
#
|
|
510
|
-
# stop
|
|
511
|
-
#
|
|
512
|
-
@cli_node.command(name='stop')
|
|
513
|
-
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
514
|
-
@click.option('--system', 'system_folders', flag_value=True,
|
|
515
|
-
help="Search for configuration in system folders instead of "
|
|
516
|
-
"user folders")
|
|
517
|
-
@click.option('--user', 'system_folders', flag_value=False, default=N_FOL,
|
|
518
|
-
help="Search for configuration in the user folders instead of "
|
|
519
|
-
"system folders. This is the default.")
|
|
520
|
-
@click.option('--all', 'all_nodes', flag_value=True,
|
|
521
|
-
help="Stop all running nodes")
|
|
522
|
-
@click.option('--force', 'force', flag_value=True,
|
|
523
|
-
help="Kill nodes instantly; don't wait for them to shut down")
|
|
524
|
-
def cli_node_stop(name: str, system_folders: bool, all_nodes: bool,
|
|
525
|
-
force: bool) -> None:
|
|
526
|
-
"""
|
|
527
|
-
Stop one or all running nodes.
|
|
528
|
-
"""
|
|
529
|
-
vnode_stop(name, system_folders, all_nodes, force)
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
def vnode_stop(name: str, system_folders: bool, all_nodes: bool,
|
|
533
|
-
force: bool) -> None:
|
|
534
|
-
"""
|
|
535
|
-
Stop a running node container.
|
|
536
|
-
|
|
537
|
-
Parameters
|
|
538
|
-
----------
|
|
539
|
-
name : str
|
|
540
|
-
Name of the configuration file.
|
|
541
|
-
system_folders : bool
|
|
542
|
-
Is this configuration stored in the system or in the user folders.
|
|
543
|
-
all_nodes : bool
|
|
544
|
-
If set to true, all running nodes will be stopped.
|
|
545
|
-
force : bool
|
|
546
|
-
If set to true, the node will not be stopped gracefully.
|
|
547
|
-
"""
|
|
548
|
-
check_docker_running()
|
|
549
|
-
client = docker.from_env()
|
|
550
|
-
|
|
551
|
-
running_node_names = _find_running_node_names(client)
|
|
552
|
-
|
|
553
|
-
if not running_node_names:
|
|
554
|
-
warning("No nodes are currently running.")
|
|
555
|
-
return
|
|
556
|
-
|
|
557
|
-
if force:
|
|
558
|
-
warning('Forcing the node to stop will not terminate helper '
|
|
559
|
-
'containers, neither will it remove routing rules made on the '
|
|
560
|
-
'host!')
|
|
561
|
-
|
|
562
|
-
if all_nodes:
|
|
563
|
-
for name in running_node_names:
|
|
564
|
-
container = client.containers.get(name)
|
|
565
|
-
if force:
|
|
566
|
-
container.kill()
|
|
567
|
-
else:
|
|
568
|
-
container.stop()
|
|
569
|
-
info(f"Stopped the {Fore.GREEN}{name}{Style.RESET_ALL} Node.")
|
|
570
|
-
else:
|
|
571
|
-
if not name:
|
|
572
|
-
name = q.select("Select the node you wish to stop:",
|
|
573
|
-
choices=running_node_names).ask()
|
|
574
|
-
else:
|
|
575
|
-
|
|
576
|
-
post_fix = "system" if system_folders else "user"
|
|
577
|
-
name = f"{APPNAME}-{name}-{post_fix}"
|
|
578
|
-
|
|
579
|
-
if name in running_node_names:
|
|
580
|
-
container = client.containers.get(name)
|
|
581
|
-
# Stop the container. Using stop() gives the container 10s to exit
|
|
582
|
-
# itself, if not then it will be killed
|
|
583
|
-
if force:
|
|
584
|
-
container.kill()
|
|
585
|
-
else:
|
|
586
|
-
container.stop()
|
|
587
|
-
info(f"Stopped the {Fore.GREEN}{name}{Style.RESET_ALL} Node.")
|
|
588
|
-
else:
|
|
589
|
-
error(f"{Fore.RED}{name}{Style.RESET_ALL} is not running?")
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
#
|
|
593
|
-
# attach
|
|
594
|
-
#
|
|
595
|
-
@cli_node.command(name='attach')
|
|
596
|
-
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
597
|
-
@click.option('--system', 'system_folders', flag_value=True,
|
|
598
|
-
help="Search for configuration in system folders rather than "
|
|
599
|
-
"user folders")
|
|
600
|
-
@click.option('--user', 'system_folders', flag_value=False, default=N_FOL,
|
|
601
|
-
help="Search for configuration in user folders rather than "
|
|
602
|
-
"system folders. This is the default")
|
|
603
|
-
def cli_node_attach(name: str, system_folders: bool) -> None:
|
|
604
|
-
"""
|
|
605
|
-
Show the node logs in the current console.
|
|
606
|
-
"""
|
|
607
|
-
check_docker_running()
|
|
608
|
-
client = docker.from_env()
|
|
609
|
-
|
|
610
|
-
running_node_names = _find_running_node_names(client)
|
|
611
|
-
|
|
612
|
-
if not running_node_names:
|
|
613
|
-
warning("No nodes are currently running. Cannot show any logs!")
|
|
614
|
-
return
|
|
615
|
-
|
|
616
|
-
if not name:
|
|
617
|
-
name = q.select("Select the node you wish to inspect:",
|
|
618
|
-
choices=running_node_names).ask()
|
|
619
|
-
else:
|
|
620
|
-
post_fix = "system" if system_folders else "user"
|
|
621
|
-
name = f"{APPNAME}-{name}-{post_fix}"
|
|
622
|
-
|
|
623
|
-
if name in running_node_names:
|
|
624
|
-
container = client.containers.get(name)
|
|
625
|
-
logs = container.attach(stream=True, logs=True)
|
|
626
|
-
Thread(target=_print_log_worker, args=(logs,), daemon=True).start()
|
|
627
|
-
while True:
|
|
628
|
-
try:
|
|
629
|
-
time.sleep(1)
|
|
630
|
-
except KeyboardInterrupt:
|
|
631
|
-
info("Closing log file. Keyboard Interrupt.")
|
|
632
|
-
info("Note that your node is still running! Shut it down with "
|
|
633
|
-
f"'{Fore.RED}vnode stop{Style.RESET_ALL}'")
|
|
634
|
-
exit(0)
|
|
635
|
-
else:
|
|
636
|
-
error(f"{Fore.RED}{name}{Style.RESET_ALL} was not running!?")
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
#
|
|
640
|
-
# create-private-key
|
|
641
|
-
#
|
|
642
|
-
@cli_node.command(name='create-private-key')
|
|
643
|
-
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
644
|
-
@click.option("-c", "--config", default=None,
|
|
645
|
-
help='Absolute path to configuration-file; overrides NAME')
|
|
646
|
-
@click.option('--system', 'system_folders', flag_value=True,
|
|
647
|
-
help="Search for configuration in system folders rather than "
|
|
648
|
-
"user folders")
|
|
649
|
-
@click.option('--user', 'system_folders', flag_value=False, default=N_FOL,
|
|
650
|
-
help="Search for configuration in user folders rather than "
|
|
651
|
-
"system folders. This is the default")
|
|
652
|
-
@click.option('--no-upload', 'upload', flag_value=False, default=True,
|
|
653
|
-
help="Don't upload the public key to the server")
|
|
654
|
-
@click.option("-o", "--organization-name", default=None,
|
|
655
|
-
help="Organization name. Used in the filename of the private key"
|
|
656
|
-
" so that it can easily be recognized again later")
|
|
657
|
-
@click.option('--overwrite', 'overwrite', flag_value=True, default=False,
|
|
658
|
-
help="Overwrite existing private key if present")
|
|
659
|
-
def cli_node_create_private_key(
|
|
660
|
-
name: str, config: str, system_folders: bool, upload: bool,
|
|
661
|
-
organization_name: str, overwrite: bool) -> None:
|
|
662
|
-
"""
|
|
663
|
-
Create and upload a new private key
|
|
664
|
-
|
|
665
|
-
Use this command with caution! Uploading a new key has several
|
|
666
|
-
consequences, e.g. you and other users of your organization
|
|
667
|
-
will no longer be able to read the results of tasks encrypted with current
|
|
668
|
-
key.
|
|
669
|
-
"""
|
|
670
|
-
NodeContext.LOGGING_ENABLED = False
|
|
671
|
-
if config:
|
|
672
|
-
name = Path(config).stem
|
|
673
|
-
ctx = NodeContext(name, system_folders, config)
|
|
674
|
-
else:
|
|
675
|
-
# retrieve context
|
|
676
|
-
name = _select_node(name, system_folders)
|
|
677
|
-
|
|
678
|
-
# raise error if config could not be found
|
|
679
|
-
if not NodeContext.config_exists(name, system_folders):
|
|
680
|
-
error(
|
|
681
|
-
f"The configuration {Fore.RED}{name}{Style.RESET_ALL} could "
|
|
682
|
-
"not be found."
|
|
683
|
-
)
|
|
684
|
-
exit(1)
|
|
685
|
-
|
|
686
|
-
# Create node context
|
|
687
|
-
ctx = NodeContext(name, system_folders)
|
|
688
|
-
|
|
689
|
-
# Authenticate with the server to obtain organization name if it wasn't
|
|
690
|
-
# provided
|
|
691
|
-
if organization_name is None:
|
|
692
|
-
client = _create_client_and_authenticate(ctx)
|
|
693
|
-
organization_name = client.whoami.organization_name
|
|
694
|
-
|
|
695
|
-
# create directory where private key goes if it doesn't exist yet
|
|
696
|
-
ctx.type_data_folder(system_folders).mkdir(parents=True, exist_ok=True)
|
|
697
|
-
|
|
698
|
-
# generate new key, and save it
|
|
699
|
-
filename = f"privkey_{organization_name}.pem"
|
|
700
|
-
file_ = ctx.type_data_folder(system_folders) / filename
|
|
701
|
-
|
|
702
|
-
if file_.exists():
|
|
703
|
-
warning(f"File '{Fore.CYAN}{file_}{Style.RESET_ALL}' exists!")
|
|
704
|
-
|
|
705
|
-
if overwrite:
|
|
706
|
-
warning("'--override' specified, so it will be overwritten ...")
|
|
707
|
-
|
|
708
|
-
if file_.exists() and not overwrite:
|
|
709
|
-
error("Could not create private key!")
|
|
710
|
-
warning(
|
|
711
|
-
"If you're **sure** you want to create a new key, "
|
|
712
|
-
"please run this command with the '--overwrite' flag"
|
|
713
|
-
)
|
|
714
|
-
warning("Continuing with existing key instead!")
|
|
715
|
-
private_key = RSACryptor(file_).private_key
|
|
716
|
-
|
|
717
|
-
else:
|
|
718
|
-
try:
|
|
719
|
-
info("Generating new private key")
|
|
720
|
-
private_key = RSACryptor.create_new_rsa_key(file_)
|
|
721
|
-
|
|
722
|
-
except Exception as e:
|
|
723
|
-
error(f"Could not create new private key '{file_}'!?")
|
|
724
|
-
debug(e)
|
|
725
|
-
info("Bailing out ...")
|
|
726
|
-
exit(1)
|
|
727
|
-
|
|
728
|
-
warning(f"Private key written to '{file_}'")
|
|
729
|
-
warning(
|
|
730
|
-
"If you're running multiple nodes, be sure to copy the private "
|
|
731
|
-
"key to the appropriate directories!"
|
|
732
|
-
)
|
|
733
|
-
|
|
734
|
-
# create public key
|
|
735
|
-
info("Deriving public key")
|
|
736
|
-
public_key = RSACryptor.create_public_key_bytes(private_key)
|
|
737
|
-
|
|
738
|
-
# update config file
|
|
739
|
-
info("Updating configuration")
|
|
740
|
-
ctx.config["encryption"]["private_key"] = str(file_)
|
|
741
|
-
ctx.config_manager.put(ctx.config)
|
|
742
|
-
ctx.config_manager.save(ctx.config_file)
|
|
743
|
-
|
|
744
|
-
# upload key to the server
|
|
745
|
-
if upload:
|
|
746
|
-
info(
|
|
747
|
-
"Uploading public key to the server. "
|
|
748
|
-
"This will overwrite any previously existing key!"
|
|
749
|
-
)
|
|
750
|
-
|
|
751
|
-
if 'client' not in locals():
|
|
752
|
-
client = _create_client_and_authenticate(ctx)
|
|
753
|
-
|
|
754
|
-
# TODO what happens if the user doesn't have permission to upload key?
|
|
755
|
-
# Does that lead to an exception or not?
|
|
756
|
-
try:
|
|
757
|
-
client.request(
|
|
758
|
-
f"/organization/{client.whoami.organization_id}",
|
|
759
|
-
method="patch",
|
|
760
|
-
json={"public_key": bytes_to_base64s(public_key)}
|
|
761
|
-
)
|
|
762
|
-
|
|
763
|
-
except Exception as e:
|
|
764
|
-
error("Could not upload the public key!")
|
|
765
|
-
debug(e)
|
|
766
|
-
exit(1)
|
|
767
|
-
|
|
768
|
-
else:
|
|
769
|
-
warning("Public key not uploaded!")
|
|
770
|
-
|
|
771
|
-
info("[Done]")
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
#
|
|
775
|
-
# clean
|
|
776
|
-
#
|
|
777
|
-
@cli_node.command(name='clean')
|
|
778
|
-
def cli_node_clean() -> None:
|
|
779
|
-
"""
|
|
780
|
-
Erase temporary Docker volumes.
|
|
781
|
-
"""
|
|
782
|
-
check_docker_running()
|
|
783
|
-
client = docker.from_env()
|
|
784
|
-
|
|
785
|
-
# retrieve all volumes
|
|
786
|
-
volumes = client.volumes.list()
|
|
787
|
-
candidates = []
|
|
788
|
-
msg = "This would remove the following volumes: "
|
|
789
|
-
for volume in volumes:
|
|
790
|
-
if volume.name[-6:] == "tmpvol":
|
|
791
|
-
candidates.append(volume)
|
|
792
|
-
msg += volume.name + ","
|
|
793
|
-
info(msg)
|
|
794
|
-
|
|
795
|
-
confirm = q.confirm("Are you sure?")
|
|
796
|
-
if confirm.ask():
|
|
797
|
-
for volume in candidates:
|
|
798
|
-
try:
|
|
799
|
-
volume.remove()
|
|
800
|
-
# info(volume.name)
|
|
801
|
-
except docker.errors.APIError as e:
|
|
802
|
-
error(f"Failed to remove volume {Fore.RED}'{volume.name}'"
|
|
803
|
-
f"{Style.RESET_ALL}. Is it still in use?")
|
|
804
|
-
debug(e)
|
|
805
|
-
exit(1)
|
|
806
|
-
info("Done!")
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
#
|
|
810
|
-
# remove
|
|
811
|
-
#
|
|
812
|
-
@cli_node.command(name="remove")
|
|
813
|
-
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
814
|
-
@click.option('--system', 'system_folders', flag_value=True,
|
|
815
|
-
help="Search for configuration in system folders rather than "
|
|
816
|
-
"user folders")
|
|
817
|
-
@click.option('--user', 'system_folders', flag_value=False, default=N_FOL,
|
|
818
|
-
help="Search for configuration in user folders rather than "
|
|
819
|
-
"system folders. This is the default")
|
|
820
|
-
@click.option('-f', "--force", type=bool, flag_value=True,
|
|
821
|
-
help='Don\'t ask for confirmation')
|
|
822
|
-
def cli_node_remove(name: str, system_folders: bool, force: bool) -> None:
|
|
823
|
-
"""
|
|
824
|
-
Delete a node permanently.
|
|
825
|
-
|
|
826
|
-
Remove the configuration file, log file, and docker volumes attached to
|
|
827
|
-
the node.
|
|
828
|
-
"""
|
|
829
|
-
vnode_remove(name, system_folders, force)
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
def vnode_remove(name: str, system_folders: bool, force: bool):
|
|
833
|
-
"""
|
|
834
|
-
Delete a node permanently
|
|
835
|
-
|
|
836
|
-
* if the node is still running, exit and tell user to run vnode stop first
|
|
837
|
-
* remove configuration file
|
|
838
|
-
* remove log file
|
|
839
|
-
* remove docker volumes attached to the node
|
|
840
|
-
|
|
841
|
-
Parameters
|
|
842
|
-
----------
|
|
843
|
-
name : str
|
|
844
|
-
Configuration name
|
|
845
|
-
system_folders : bool
|
|
846
|
-
If True, use system folders, otherwise use user folders
|
|
847
|
-
force : bool
|
|
848
|
-
If True, don't ask for confirmation before removing the node
|
|
849
|
-
"""
|
|
850
|
-
# select configuration name if none supplied
|
|
851
|
-
name = _select_node(name, system_folders)
|
|
852
|
-
|
|
853
|
-
client = docker.from_env()
|
|
854
|
-
check_if_docker_daemon_is_running(client)
|
|
855
|
-
|
|
856
|
-
# check if node is still running, otherwise don't allow deleting it
|
|
857
|
-
running_node_names = _find_running_node_names(client)
|
|
858
|
-
|
|
859
|
-
post_fix = "system" if system_folders else "user"
|
|
860
|
-
node_container_name = f"{APPNAME}-{name}-{post_fix}"
|
|
861
|
-
if node_container_name in running_node_names:
|
|
862
|
-
error(f"Node {name} is still running! Please stop the node before "
|
|
863
|
-
"deleting it.")
|
|
864
|
-
exit(1)
|
|
865
|
-
|
|
866
|
-
if not force:
|
|
867
|
-
if not q.confirm(
|
|
868
|
-
"This node will be deleted permanently including its "
|
|
869
|
-
"configuration. Are you sure?", default=False
|
|
870
|
-
).ask():
|
|
871
|
-
info("Node will not be deleted")
|
|
872
|
-
exit(0)
|
|
873
|
-
|
|
874
|
-
# create node context
|
|
875
|
-
ctx = NodeContext(name, system_folders=system_folders)
|
|
876
|
-
|
|
877
|
-
# remove the docker volume and any temporary volumes
|
|
878
|
-
debug("Deleting docker volumes")
|
|
879
|
-
volumes = client.volumes.list()
|
|
880
|
-
for vol in volumes:
|
|
881
|
-
if vol.name.startswith(ctx.docker_volume_name): # includes tmp volumes
|
|
882
|
-
info(f"Deleting docker volume {vol.name}")
|
|
883
|
-
vol.remove()
|
|
884
|
-
# remove docker vpn volume
|
|
885
|
-
if vol.name == ctx.docker_vpn_volume_name:
|
|
886
|
-
info(f"Deleting VPN docker volume {vol.name}")
|
|
887
|
-
vol.remove()
|
|
888
|
-
|
|
889
|
-
# remove the VPN configuration file
|
|
890
|
-
vpn_config_file = os.path.join(ctx.data_dir, 'vpn', VPN_CONFIG_FILE)
|
|
891
|
-
remove_file(vpn_config_file, 'VPN configuration')
|
|
892
|
-
|
|
893
|
-
# remove the config file
|
|
894
|
-
remove_file(ctx.config_file, 'configuration')
|
|
895
|
-
|
|
896
|
-
# remove the log file. As this process opens the log file above, the log
|
|
897
|
-
# handlers need to be closed before deleting
|
|
898
|
-
info(f"Removing log file {ctx.log_file}")
|
|
899
|
-
for handler in itertools.chain(ctx.log.handlers, ctx.log.root.handlers):
|
|
900
|
-
handler.close()
|
|
901
|
-
# remove_file(ctx.log_file, 'log')
|
|
902
|
-
|
|
903
|
-
# removes the whole folder
|
|
904
|
-
rmtree(Path(ctx.log_file.parent))
|
|
905
|
-
|
|
906
|
-
# remove the folder: if it hasn't been started yet this won't exist...
|
|
907
|
-
if Path.exists(ctx.config_dir / name):
|
|
908
|
-
rmtree(ctx.config_dir / name)
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
#
|
|
912
|
-
# version
|
|
913
|
-
#
|
|
914
|
-
@cli_node.command(name='version')
|
|
915
|
-
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
916
|
-
@click.option('--system', 'system_folders', flag_value=True,
|
|
917
|
-
help="Search for configuration in system folders rather than "
|
|
918
|
-
"user folders")
|
|
919
|
-
@click.option('--user', 'system_folders', flag_value=False, default=N_FOL,
|
|
920
|
-
help="Search for configuration in user folders rather than "
|
|
921
|
-
"system folders. This is the default")
|
|
922
|
-
def cli_node_version(name: str, system_folders: bool) -> None:
|
|
923
|
-
"""
|
|
924
|
-
Returns current version of a vantage6 node.
|
|
925
|
-
"""
|
|
926
|
-
check_docker_running()
|
|
927
|
-
client = docker.from_env()
|
|
928
|
-
|
|
929
|
-
running_node_names = _find_running_node_names(client)
|
|
930
|
-
|
|
931
|
-
if not name:
|
|
932
|
-
if not running_node_names:
|
|
933
|
-
error("No nodes are running! You can only check the version for "
|
|
934
|
-
"nodes that are running")
|
|
935
|
-
exit(1)
|
|
936
|
-
name = q.select("Select the node you wish to inspect:",
|
|
937
|
-
choices=running_node_names).ask()
|
|
938
|
-
else:
|
|
939
|
-
post_fix = "system" if system_folders else "user"
|
|
940
|
-
name = f"{APPNAME}-{name}-{post_fix}"
|
|
941
|
-
|
|
942
|
-
if name in running_node_names:
|
|
943
|
-
container = client.containers.get(name)
|
|
944
|
-
version = container.exec_run(cmd='vnode-local version', stdout=True)
|
|
945
|
-
click.echo(
|
|
946
|
-
{"node": version.output.decode('utf-8'), "cli": __version__})
|
|
947
|
-
else:
|
|
948
|
-
error(f"Node {name} is not running! Cannot provide version...")
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
#
|
|
952
|
-
# set-api-key
|
|
953
|
-
#
|
|
954
|
-
@cli_node.command(name='set-api-key')
|
|
955
|
-
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
956
|
-
@click.option("--api-key", default=None, help="New API key")
|
|
957
|
-
@click.option('--system', 'system_folders', flag_value=True,
|
|
958
|
-
help="Search for configuration in system folders rather than "
|
|
959
|
-
"user folders")
|
|
960
|
-
@click.option('--user', 'system_folders', flag_value=False, default=N_FOL,
|
|
961
|
-
help="Search for configuration in user folders rather than "
|
|
962
|
-
"system folders. This is the default")
|
|
963
|
-
def cli_node_set_api_key(name: str, api_key: str,
|
|
964
|
-
system_folders: bool) -> None:
|
|
965
|
-
"""
|
|
966
|
-
Put a new API key into the node configuration file
|
|
967
|
-
"""
|
|
968
|
-
# select node name
|
|
969
|
-
name = _select_node(name, system_folders)
|
|
970
|
-
|
|
971
|
-
# Check that we can write in the config folder
|
|
972
|
-
if not check_config_writeable(system_folders):
|
|
973
|
-
error("Your user does not have write access to all folders. Exiting")
|
|
974
|
-
exit(1)
|
|
975
|
-
|
|
976
|
-
if not api_key:
|
|
977
|
-
api_key = q.text("Please enter your new API key:").ask()
|
|
978
|
-
|
|
979
|
-
# get configuration manager
|
|
980
|
-
ctx = NodeContext(name, system_folders=system_folders)
|
|
981
|
-
conf_mgr = NodeConfigurationManager.from_file(ctx.config_file)
|
|
982
|
-
|
|
983
|
-
# set new api key, and save the file
|
|
984
|
-
ctx.config['api_key'] = api_key
|
|
985
|
-
conf_mgr.put(ctx.config)
|
|
986
|
-
conf_mgr.save(ctx.config_file)
|
|
987
|
-
info("Your new API key has been uploaded to the config file "
|
|
988
|
-
f"{ctx.config_file}.")
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
# helper functions
|
|
992
|
-
def _print_log_worker(logs_stream: Iterable[bytes]) -> None:
|
|
993
|
-
"""
|
|
994
|
-
Print the logs from the logs stream.
|
|
995
|
-
|
|
996
|
-
Parameters
|
|
997
|
-
----------
|
|
998
|
-
logs_stream : Iterable[bytes]
|
|
999
|
-
Output of the container.attach() method
|
|
1000
|
-
"""
|
|
1001
|
-
for log in logs_stream:
|
|
1002
|
-
print(log.decode(STRING_ENCODING), end="")
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
def _create_client(ctx: NodeContext) -> UserClient:
|
|
1006
|
-
"""
|
|
1007
|
-
Create a client instance.
|
|
1008
|
-
|
|
1009
|
-
Parameters
|
|
1010
|
-
----------
|
|
1011
|
-
ctx : NodeContext
|
|
1012
|
-
Context of the node loaded from the configuration file
|
|
1013
|
-
Returns
|
|
1014
|
-
-------
|
|
1015
|
-
UserClient
|
|
1016
|
-
vantage6 client
|
|
1017
|
-
"""
|
|
1018
|
-
host = ctx.config['server_url']
|
|
1019
|
-
# if the server is run locally, we need to use localhost here instead of
|
|
1020
|
-
# the host address of docker
|
|
1021
|
-
if host in ['http://host.docker.internal', 'http://172.17.0.1']:
|
|
1022
|
-
host = 'http://localhost'
|
|
1023
|
-
port = ctx.config['port']
|
|
1024
|
-
api_path = ctx.config['api_path']
|
|
1025
|
-
info(f"Connecting to server at '{host}:{port}{api_path}'")
|
|
1026
|
-
return UserClient(host, port, api_path, log_level='warn')
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
def _create_client_and_authenticate(ctx: NodeContext) -> UserClient:
|
|
1030
|
-
"""
|
|
1031
|
-
Generate a client and authenticate with the server.
|
|
1032
|
-
|
|
1033
|
-
Parameters
|
|
1034
|
-
----------
|
|
1035
|
-
ctx : NodeContext
|
|
1036
|
-
Context of the node loaded from the configuration file
|
|
1037
|
-
|
|
1038
|
-
Returns
|
|
1039
|
-
-------
|
|
1040
|
-
UserClient
|
|
1041
|
-
vantage6 client
|
|
1042
|
-
"""
|
|
1043
|
-
client = _create_client(ctx)
|
|
1044
|
-
|
|
1045
|
-
username = q.text("Username:").ask()
|
|
1046
|
-
password = q.password("Password:").ask()
|
|
1047
|
-
|
|
1048
|
-
try:
|
|
1049
|
-
client.authenticate(username, password)
|
|
1050
|
-
|
|
1051
|
-
except Exception as exc:
|
|
1052
|
-
error("Could not authenticate with server!")
|
|
1053
|
-
debug(exc)
|
|
1054
|
-
exit(1)
|
|
1055
|
-
|
|
1056
|
-
return client
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
def _select_node(name: str, system_folders: bool) -> tuple[str, str]:
|
|
1060
|
-
"""
|
|
1061
|
-
Let user select node through questionnaire if name is not given.
|
|
1062
|
-
|
|
1063
|
-
Returns
|
|
1064
|
-
-------
|
|
1065
|
-
str
|
|
1066
|
-
Name of the configuration file
|
|
1067
|
-
"""
|
|
1068
|
-
name = name if name else \
|
|
1069
|
-
select_configuration_questionaire("node", system_folders)
|
|
1070
|
-
|
|
1071
|
-
# raise error if config could not be found
|
|
1072
|
-
if not NodeContext.config_exists(name, system_folders):
|
|
1073
|
-
error(
|
|
1074
|
-
f"The configuration {Fore.RED}{name}{Style.RESET_ALL} could "
|
|
1075
|
-
f"not be found."
|
|
1076
|
-
)
|
|
1077
|
-
exit(1)
|
|
1078
|
-
return name
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
def _find_running_node_names(client: docker.DockerClient) -> list[str]:
|
|
1082
|
-
"""
|
|
1083
|
-
Returns a list of names of running nodes.
|
|
1084
|
-
|
|
1085
|
-
Parameters
|
|
1086
|
-
----------
|
|
1087
|
-
client : docker.DockerClient
|
|
1088
|
-
Docker client instance
|
|
1089
|
-
|
|
1090
|
-
Returns
|
|
1091
|
-
-------
|
|
1092
|
-
list[str]
|
|
1093
|
-
List of names of running nodes
|
|
1094
|
-
"""
|
|
1095
|
-
running_nodes = client.containers.list(
|
|
1096
|
-
filters={"label": f"{APPNAME}-type=node"})
|
|
1097
|
-
return [node.name for node in running_nodes]
|