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
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from threading import Thread
|
|
4
|
+
import time
|
|
5
|
+
import os.path
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import questionary as q
|
|
9
|
+
import docker
|
|
10
|
+
|
|
11
|
+
from colorama import Fore, Style
|
|
12
|
+
|
|
13
|
+
from vantage6.common import (
|
|
14
|
+
warning, error, info, debug,
|
|
15
|
+
get_database_config
|
|
16
|
+
)
|
|
17
|
+
from vantage6.common.globals import (
|
|
18
|
+
APPNAME,
|
|
19
|
+
DEFAULT_DOCKER_REGISTRY,
|
|
20
|
+
DEFAULT_NODE_IMAGE,
|
|
21
|
+
DEFAULT_NODE_IMAGE_WO_TAG,
|
|
22
|
+
)
|
|
23
|
+
from vantage6.common.docker.addons import (
|
|
24
|
+
pull_if_newer,
|
|
25
|
+
remove_container_if_exists,
|
|
26
|
+
check_docker_running
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
from vantage6.cli.context import NodeContext
|
|
30
|
+
from vantage6.cli.globals import (
|
|
31
|
+
DEFAULT_NODE_SYSTEM_FOLDERS as N_FOL
|
|
32
|
+
)
|
|
33
|
+
from vantage6.cli.configuration_wizard import (
|
|
34
|
+
configuration_wizard,
|
|
35
|
+
select_configuration_questionaire,
|
|
36
|
+
)
|
|
37
|
+
from vantage6.cli.utils import check_config_name_allowed
|
|
38
|
+
from vantage6.cli import __version__
|
|
39
|
+
from vantage6.cli.node.common import print_log_worker, create_client
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@click.command()
|
|
43
|
+
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
44
|
+
@click.option("-c", "--config", default=None,
|
|
45
|
+
help='Absolute path to configuration-file; overrides NAME')
|
|
46
|
+
@click.option('--system', 'system_folders', flag_value=True,
|
|
47
|
+
help="Search for the configuration in the system folders")
|
|
48
|
+
@click.option('--user', 'system_folders', flag_value=False, default=N_FOL,
|
|
49
|
+
help="Search for the configuration in the user folders. This is "
|
|
50
|
+
"the default")
|
|
51
|
+
@click.option('-i', '--image', default=None, help="Node Docker image to use")
|
|
52
|
+
@click.option('--keep/--auto-remove', default=False,
|
|
53
|
+
help="Keep node container after finishing. Useful for debugging")
|
|
54
|
+
@click.option('--force-db-mount', is_flag=True,
|
|
55
|
+
help="Always mount node databases; skip the check if they are "
|
|
56
|
+
"existing files.")
|
|
57
|
+
@click.option('--attach/--detach', default=False,
|
|
58
|
+
help="Show node logs on the current console after starting the "
|
|
59
|
+
"node")
|
|
60
|
+
@click.option('--mount-src', default='',
|
|
61
|
+
help="Override vantage6 source code in container with the source"
|
|
62
|
+
" code in this path")
|
|
63
|
+
def cli_node_start(name: str, config: str, system_folders: bool, image: str,
|
|
64
|
+
keep: bool, mount_src: str, attach: bool,
|
|
65
|
+
force_db_mount: bool) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Start the node.
|
|
68
|
+
"""
|
|
69
|
+
check_docker_running()
|
|
70
|
+
info("Starting node...")
|
|
71
|
+
info("Finding Docker daemon")
|
|
72
|
+
docker_client = docker.from_env()
|
|
73
|
+
NodeContext.LOGGING_ENABLED = False
|
|
74
|
+
if config:
|
|
75
|
+
name = Path(config).stem
|
|
76
|
+
ctx = NodeContext(name, system_folders, config)
|
|
77
|
+
|
|
78
|
+
else:
|
|
79
|
+
# in case no name is supplied, ask the user to select one
|
|
80
|
+
if not name:
|
|
81
|
+
name = select_configuration_questionaire("node", system_folders)
|
|
82
|
+
|
|
83
|
+
# check that config exists, if not a questionaire will be invoked
|
|
84
|
+
if not NodeContext.config_exists(name, system_folders):
|
|
85
|
+
warning(f"Configuration {Fore.RED}{name}{Style.RESET_ALL} does not"
|
|
86
|
+
" exist.")
|
|
87
|
+
|
|
88
|
+
if q.confirm("Create this configuration now?").ask():
|
|
89
|
+
configuration_wizard("node", name, system_folders)
|
|
90
|
+
|
|
91
|
+
else:
|
|
92
|
+
error("Config file couldn't be loaded")
|
|
93
|
+
sys.exit(0)
|
|
94
|
+
|
|
95
|
+
ctx = NodeContext(name, system_folders)
|
|
96
|
+
|
|
97
|
+
# check if config name is allowed docker name, else exit
|
|
98
|
+
check_config_name_allowed(ctx.name)
|
|
99
|
+
|
|
100
|
+
# check that this node is not already running
|
|
101
|
+
running_nodes = docker_client.containers.list(
|
|
102
|
+
filters={"label": f"{APPNAME}-type=node"}
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
suffix = "system" if system_folders else "user"
|
|
106
|
+
for node in running_nodes:
|
|
107
|
+
if node.name == f"{APPNAME}-{name}-{suffix}":
|
|
108
|
+
error(f"Node {Fore.RED}{name}{Style.RESET_ALL} is already running")
|
|
109
|
+
exit(1)
|
|
110
|
+
|
|
111
|
+
# make sure the (host)-task and -log dir exists
|
|
112
|
+
info("Checking that data and log dirs exist")
|
|
113
|
+
ctx.data_dir.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
ctx.log_dir.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
|
|
116
|
+
# Determine image-name. First we check if the option --image has been used.
|
|
117
|
+
# Then we check if the image has been specified in the config file, and
|
|
118
|
+
# finally we use the default settings from the package.
|
|
119
|
+
if not image:
|
|
120
|
+
custom_images: dict = ctx.config.get('images')
|
|
121
|
+
if custom_images:
|
|
122
|
+
image = custom_images.get("node")
|
|
123
|
+
else:
|
|
124
|
+
# if no custom image is specified, find the server version and use
|
|
125
|
+
# the latest images from that minor version
|
|
126
|
+
client = create_client(ctx)
|
|
127
|
+
major_minor = None
|
|
128
|
+
try:
|
|
129
|
+
# try to get server version, skip if can't get a connection
|
|
130
|
+
version = client.util.get_server_version(
|
|
131
|
+
attempts_on_timeout=3
|
|
132
|
+
)['version']
|
|
133
|
+
major_minor = '.'.join(version.split('.')[:2])
|
|
134
|
+
image = (f"{DEFAULT_DOCKER_REGISTRY}/"
|
|
135
|
+
f"{DEFAULT_NODE_IMAGE_WO_TAG}"
|
|
136
|
+
f":{major_minor}")
|
|
137
|
+
except Exception:
|
|
138
|
+
warning("Could not determine server version. Using default "
|
|
139
|
+
"node image")
|
|
140
|
+
|
|
141
|
+
if major_minor and not __version__.startswith(major_minor):
|
|
142
|
+
warning(
|
|
143
|
+
"Version mismatch between CLI and server/node. CLI is "
|
|
144
|
+
f"running on version {__version__}, while node and server "
|
|
145
|
+
f"are on version {major_minor}. This might cause "
|
|
146
|
+
f"unexpected issues; changing to {major_minor}.<latest> "
|
|
147
|
+
"is recommended."
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# fail safe, in case no custom image is specified and we can't get the
|
|
151
|
+
# server version
|
|
152
|
+
if not image:
|
|
153
|
+
image = f"{DEFAULT_DOCKER_REGISTRY}/{DEFAULT_NODE_IMAGE}"
|
|
154
|
+
|
|
155
|
+
info(f"Pulling latest node image '{image}'")
|
|
156
|
+
try:
|
|
157
|
+
# docker_client.images.pull(image)
|
|
158
|
+
pull_if_newer(docker.from_env(), image)
|
|
159
|
+
|
|
160
|
+
except Exception as e:
|
|
161
|
+
warning(' ... Getting latest node image failed:')
|
|
162
|
+
warning(f" {e}")
|
|
163
|
+
else:
|
|
164
|
+
info(" ... success!")
|
|
165
|
+
|
|
166
|
+
info("Creating Docker data volume")
|
|
167
|
+
|
|
168
|
+
data_volume = docker_client.volumes.create(ctx.docker_volume_name)
|
|
169
|
+
vpn_volume = docker_client.volumes.create(ctx.docker_vpn_volume_name)
|
|
170
|
+
ssh_volume = docker_client.volumes.create(ctx.docker_ssh_volume_name)
|
|
171
|
+
squid_volume = docker_client.volumes.create(ctx.docker_squid_volume_name)
|
|
172
|
+
|
|
173
|
+
info("Creating file & folder mounts")
|
|
174
|
+
# FIXME: should obtain mount points from DockerNodeContext
|
|
175
|
+
mounts = [
|
|
176
|
+
# (target, source)
|
|
177
|
+
("/mnt/log", str(ctx.log_dir)),
|
|
178
|
+
("/mnt/data", data_volume.name),
|
|
179
|
+
("/mnt/vpn", vpn_volume.name),
|
|
180
|
+
("/mnt/ssh", ssh_volume.name),
|
|
181
|
+
("/mnt/squid", squid_volume.name),
|
|
182
|
+
("/mnt/config", str(ctx.config_dir)),
|
|
183
|
+
("/var/run/docker.sock", "/var/run/docker.sock"),
|
|
184
|
+
]
|
|
185
|
+
|
|
186
|
+
if mount_src:
|
|
187
|
+
# If mount_src is a relative path, docker will consider it a volume.
|
|
188
|
+
mount_src = os.path.abspath(mount_src)
|
|
189
|
+
mounts.append(('/vantage6', mount_src))
|
|
190
|
+
|
|
191
|
+
# FIXME: Code duplication: Node.__init__() (vantage6/node/__init__.py)
|
|
192
|
+
# uses a lot of the same logic. Suggest moving this to
|
|
193
|
+
# ctx.get_private_key()
|
|
194
|
+
filename = ctx.config.get("encryption", {}).get("private_key")
|
|
195
|
+
# filename may be set to an empty string
|
|
196
|
+
if not filename:
|
|
197
|
+
filename = 'private_key.pem'
|
|
198
|
+
|
|
199
|
+
# Location may be overridden by the environment
|
|
200
|
+
filename = os.environ.get('PRIVATE_KEY', filename)
|
|
201
|
+
|
|
202
|
+
# If ctx.get_data_file() receives an absolute path, it is returned as-is
|
|
203
|
+
fullpath = Path(ctx.get_data_file(filename))
|
|
204
|
+
if fullpath:
|
|
205
|
+
if Path(fullpath).exists():
|
|
206
|
+
mounts.append(("/mnt/private_key.pem", str(fullpath)))
|
|
207
|
+
else:
|
|
208
|
+
warning(f"private key file provided {fullpath}, "
|
|
209
|
+
"but does not exists")
|
|
210
|
+
|
|
211
|
+
# Mount private keys for ssh tunnels
|
|
212
|
+
ssh_tunnels = ctx.config.get("ssh-tunnels", [])
|
|
213
|
+
for ssh_tunnel in ssh_tunnels:
|
|
214
|
+
hostname = ssh_tunnel.get("hostname")
|
|
215
|
+
key_path = ssh_tunnel.get("ssh", {}).get("identity", {}).get("key")
|
|
216
|
+
if not key_path:
|
|
217
|
+
error(f"SSH tunnel identity {Fore.RED}{hostname}{Style.RESET_ALL} "
|
|
218
|
+
"key not provided. Continuing to start without this tunnel.")
|
|
219
|
+
key_path = Path(key_path)
|
|
220
|
+
if not key_path.exists():
|
|
221
|
+
error(f"SSH tunnel identity {Fore.RED}{hostname}{Style.RESET_ALL} "
|
|
222
|
+
"key does not exist. Continuing to start without this "
|
|
223
|
+
"tunnel.")
|
|
224
|
+
|
|
225
|
+
info(f" Mounting private key for {hostname} at {key_path}")
|
|
226
|
+
|
|
227
|
+
# we remove the .tmp in the container, this is because the file is
|
|
228
|
+
# mounted in a volume mount point. Somehow the file is than empty in
|
|
229
|
+
# the volume but not for the node instance. By removing the .tmp we
|
|
230
|
+
# make sure that the file is not empty in the volume.
|
|
231
|
+
mounts.append((f"/mnt/ssh/{hostname}.pem.tmp", str(key_path)))
|
|
232
|
+
|
|
233
|
+
env = {
|
|
234
|
+
"DATA_VOLUME_NAME": data_volume.name,
|
|
235
|
+
"VPN_VOLUME_NAME": vpn_volume.name,
|
|
236
|
+
"PRIVATE_KEY": "/mnt/private_key.pem"
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
# only mount the DB if it is a file
|
|
240
|
+
info("Setting up databases")
|
|
241
|
+
db_labels = [db['label'] for db in ctx.databases]
|
|
242
|
+
for label in db_labels:
|
|
243
|
+
|
|
244
|
+
db_config = get_database_config(ctx.databases, label)
|
|
245
|
+
uri = db_config['uri']
|
|
246
|
+
db_type = db_config['type']
|
|
247
|
+
|
|
248
|
+
info(f" Processing {Fore.GREEN}{db_type}{Style.RESET_ALL} database "
|
|
249
|
+
f"{Fore.GREEN}{label}:{uri}{Style.RESET_ALL}")
|
|
250
|
+
label_capitals = label.upper()
|
|
251
|
+
|
|
252
|
+
try:
|
|
253
|
+
file_based = Path(uri).exists()
|
|
254
|
+
except Exception:
|
|
255
|
+
# If the database uri cannot be parsed, it is definitely not a
|
|
256
|
+
# file. In case of http servers or sql servers, checking the path
|
|
257
|
+
# of the the uri will lead to an OS-dependent error, which is why
|
|
258
|
+
# we catch all exceptions here.
|
|
259
|
+
file_based = False
|
|
260
|
+
|
|
261
|
+
if not file_based and not force_db_mount:
|
|
262
|
+
debug(' - non file-based database added')
|
|
263
|
+
env[f'{label_capitals}_DATABASE_URI'] = uri
|
|
264
|
+
else:
|
|
265
|
+
debug(' - file-based database added')
|
|
266
|
+
suffix = Path(uri).suffix
|
|
267
|
+
env[f'{label_capitals}_DATABASE_URI'] = f'{label}{suffix}'
|
|
268
|
+
mounts.append((f'/mnt/{label}{suffix}', str(uri)))
|
|
269
|
+
|
|
270
|
+
system_folders_option = "--system" if system_folders else "--user"
|
|
271
|
+
cmd = f'vnode-local start -c /mnt/config/{name}.yaml -n {name} '\
|
|
272
|
+
f' --dockerized {system_folders_option}'
|
|
273
|
+
|
|
274
|
+
info("Running Docker container")
|
|
275
|
+
volumes = []
|
|
276
|
+
for mount in mounts:
|
|
277
|
+
volumes.append(f'{mount[1]}:{mount[0]}')
|
|
278
|
+
|
|
279
|
+
remove_container_if_exists(
|
|
280
|
+
docker_client=docker_client, name=ctx.docker_container_name
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
container = docker_client.containers.run(
|
|
284
|
+
image,
|
|
285
|
+
command=cmd,
|
|
286
|
+
volumes=volumes,
|
|
287
|
+
detach=True,
|
|
288
|
+
labels={
|
|
289
|
+
f"{APPNAME}-type": "node",
|
|
290
|
+
"system": str(system_folders),
|
|
291
|
+
"name": ctx.config_file_name
|
|
292
|
+
},
|
|
293
|
+
environment=env,
|
|
294
|
+
name=ctx.docker_container_name,
|
|
295
|
+
auto_remove=not keep,
|
|
296
|
+
tty=True
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
info(f"Success! container id = {container}")
|
|
300
|
+
|
|
301
|
+
if attach:
|
|
302
|
+
logs = container.attach(stream=True, logs=True)
|
|
303
|
+
Thread(target=print_log_worker, args=(logs,), daemon=True).start()
|
|
304
|
+
while True:
|
|
305
|
+
try:
|
|
306
|
+
time.sleep(1)
|
|
307
|
+
except KeyboardInterrupt:
|
|
308
|
+
info("Closing log file. Keyboard Interrupt.")
|
|
309
|
+
info("Note that your node is still running! Shut it down with "
|
|
310
|
+
f"'{Fore.RED}v6 node stop{Style.RESET_ALL}'")
|
|
311
|
+
exit(0)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import questionary as q
|
|
3
|
+
import docker
|
|
4
|
+
|
|
5
|
+
from colorama import Fore, Style
|
|
6
|
+
|
|
7
|
+
from vantage6.common import warning, error, info
|
|
8
|
+
from vantage6.common.globals import APPNAME
|
|
9
|
+
from vantage6.common.docker.addons import check_docker_running
|
|
10
|
+
from vantage6.cli.globals import DEFAULT_NODE_SYSTEM_FOLDERS as N_FOL
|
|
11
|
+
|
|
12
|
+
from vantage6.cli.node.common import find_running_node_names
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@click.command()
|
|
16
|
+
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
17
|
+
@click.option('--system', 'system_folders', flag_value=True,
|
|
18
|
+
help="Search for configuration in system folders instead of "
|
|
19
|
+
"user folders")
|
|
20
|
+
@click.option('--user', 'system_folders', flag_value=False, default=N_FOL,
|
|
21
|
+
help="Search for configuration in the user folders instead of "
|
|
22
|
+
"system folders. This is the default.")
|
|
23
|
+
@click.option('--all', 'all_nodes', flag_value=True,
|
|
24
|
+
help="Stop all running nodes")
|
|
25
|
+
@click.option('--force', 'force', flag_value=True,
|
|
26
|
+
help="Kill nodes instantly; don't wait for them to shut down")
|
|
27
|
+
def cli_node_stop(name: str, system_folders: bool, all_nodes: bool,
|
|
28
|
+
force: bool) -> None:
|
|
29
|
+
"""
|
|
30
|
+
Stop one or all running nodes.
|
|
31
|
+
"""
|
|
32
|
+
check_docker_running()
|
|
33
|
+
client = docker.from_env()
|
|
34
|
+
|
|
35
|
+
running_node_names = find_running_node_names(client)
|
|
36
|
+
|
|
37
|
+
if not running_node_names:
|
|
38
|
+
warning("No nodes are currently running.")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
if force:
|
|
42
|
+
warning('Forcing the node to stop will not terminate helper '
|
|
43
|
+
'containers, neither will it remove routing rules made on the '
|
|
44
|
+
'host!')
|
|
45
|
+
|
|
46
|
+
if all_nodes:
|
|
47
|
+
for name in running_node_names:
|
|
48
|
+
container = client.containers.get(name)
|
|
49
|
+
if force:
|
|
50
|
+
container.kill()
|
|
51
|
+
else:
|
|
52
|
+
container.stop()
|
|
53
|
+
info(f"Stopped the {Fore.GREEN}{name}{Style.RESET_ALL} Node.")
|
|
54
|
+
else:
|
|
55
|
+
if not name:
|
|
56
|
+
name = q.select("Select the node you wish to stop:",
|
|
57
|
+
choices=running_node_names).ask()
|
|
58
|
+
else:
|
|
59
|
+
|
|
60
|
+
post_fix = "system" if system_folders else "user"
|
|
61
|
+
name = f"{APPNAME}-{name}-{post_fix}"
|
|
62
|
+
|
|
63
|
+
if name in running_node_names:
|
|
64
|
+
container = client.containers.get(name)
|
|
65
|
+
# Stop the container. Using stop() gives the container 10s to exit
|
|
66
|
+
# itself, if not then it will be killed
|
|
67
|
+
if force:
|
|
68
|
+
container.kill()
|
|
69
|
+
else:
|
|
70
|
+
container.stop()
|
|
71
|
+
info(f"Stopped the {Fore.GREEN}{name}{Style.RESET_ALL} Node.")
|
|
72
|
+
else:
|
|
73
|
+
error(f"{Fore.RED}{name}{Style.RESET_ALL} is not running?")
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import questionary as q
|
|
3
|
+
import docker
|
|
4
|
+
|
|
5
|
+
from vantage6.common import error
|
|
6
|
+
from vantage6.common.globals import APPNAME
|
|
7
|
+
from vantage6.common.docker.addons import check_docker_running
|
|
8
|
+
from vantage6.cli.globals import DEFAULT_NODE_SYSTEM_FOLDERS as N_FOL
|
|
9
|
+
from vantage6.cli import __version__
|
|
10
|
+
from vantage6.cli.node.common import find_running_node_names
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@click.command()
|
|
14
|
+
@click.option("-n", "--name", default=None, help="Configuration name")
|
|
15
|
+
@click.option('--system', 'system_folders', flag_value=True,
|
|
16
|
+
help="Search for configuration in system folders rather than "
|
|
17
|
+
"user folders")
|
|
18
|
+
@click.option('--user', 'system_folders', flag_value=False, default=N_FOL,
|
|
19
|
+
help="Search for configuration in user folders rather than "
|
|
20
|
+
"system folders. This is the default")
|
|
21
|
+
def cli_node_version(name: str, system_folders: bool) -> None:
|
|
22
|
+
"""
|
|
23
|
+
Returns current version of a vantage6 node.
|
|
24
|
+
"""
|
|
25
|
+
check_docker_running()
|
|
26
|
+
client = docker.from_env()
|
|
27
|
+
|
|
28
|
+
running_node_names = find_running_node_names(client)
|
|
29
|
+
|
|
30
|
+
if not name:
|
|
31
|
+
if not running_node_names:
|
|
32
|
+
error("No nodes are running! You can only check the version for "
|
|
33
|
+
"nodes that are running")
|
|
34
|
+
exit(1)
|
|
35
|
+
name = q.select("Select the node you wish to inspect:",
|
|
36
|
+
choices=running_node_names).ask()
|
|
37
|
+
else:
|
|
38
|
+
post_fix = "system" if system_folders else "user"
|
|
39
|
+
name = f"{APPNAME}-{name}-{post_fix}"
|
|
40
|
+
|
|
41
|
+
if name in running_node_names:
|
|
42
|
+
container = client.containers.get(name)
|
|
43
|
+
version = container.exec_run(cmd='vnode-local version', stdout=True)
|
|
44
|
+
click.echo(
|
|
45
|
+
{"node": version.output.decode('utf-8'), "cli": __version__})
|
|
46
|
+
else:
|
|
47
|
+
error(f"Node {name} is not running! Cannot provide version...")
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from threading import Thread
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
import questionary as q
|
|
6
|
+
import docker
|
|
7
|
+
|
|
8
|
+
from colorama import (Fore, Style)
|
|
9
|
+
|
|
10
|
+
from vantage6.common import info, error
|
|
11
|
+
from vantage6.common.docker.addons import check_docker_running
|
|
12
|
+
from vantage6.common.globals import APPNAME
|
|
13
|
+
|
|
14
|
+
from vantage6.cli.globals import DEFAULT_SERVER_SYSTEM_FOLDERS
|
|
15
|
+
from vantage6.cli.server.common import print_log_worker
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@click.command()
|
|
19
|
+
@click.option("-n", "--name", default=None, help="configuration name")
|
|
20
|
+
@click.option('--system', 'system_folders', flag_value=True)
|
|
21
|
+
@click.option('--user', 'system_folders', flag_value=False,
|
|
22
|
+
default=DEFAULT_SERVER_SYSTEM_FOLDERS)
|
|
23
|
+
def cli_server_attach(name: str, system_folders: bool) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Show the server logs in the current console.
|
|
26
|
+
"""
|
|
27
|
+
check_docker_running()
|
|
28
|
+
client = docker.from_env()
|
|
29
|
+
|
|
30
|
+
running_servers = client.containers.list(
|
|
31
|
+
filters={"label": f"{APPNAME}-type=server"})
|
|
32
|
+
running_server_names = [node.name for node in running_servers]
|
|
33
|
+
|
|
34
|
+
if not name:
|
|
35
|
+
name = q.select("Select the server you wish to inspect:",
|
|
36
|
+
choices=running_server_names).ask()
|
|
37
|
+
else:
|
|
38
|
+
post_fix = "system" if system_folders else "user"
|
|
39
|
+
name = f"{APPNAME}-{name}-{post_fix}-server"
|
|
40
|
+
|
|
41
|
+
if name in running_server_names:
|
|
42
|
+
container = client.containers.get(name)
|
|
43
|
+
logs = container.attach(stream=True, logs=True, stdout=True)
|
|
44
|
+
Thread(target=print_log_worker, args=(logs,), daemon=True).start()
|
|
45
|
+
while True:
|
|
46
|
+
try:
|
|
47
|
+
time.sleep(1)
|
|
48
|
+
except KeyboardInterrupt:
|
|
49
|
+
info("Closing log file. Keyboard Interrupt.")
|
|
50
|
+
info("Note that your server is still running! Shut it down "
|
|
51
|
+
f"with {Fore.RED}v6 server stop{Style.RESET_ALL}")
|
|
52
|
+
exit(0)
|
|
53
|
+
else:
|
|
54
|
+
error(f"{Fore.RED}{name}{Style.RESET_ALL} was not running!?")
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from typing import Iterable
|
|
2
|
+
from functools import wraps
|
|
3
|
+
|
|
4
|
+
from docker.client import DockerClient
|
|
5
|
+
import click
|
|
6
|
+
from colorama import Fore, Style
|
|
7
|
+
|
|
8
|
+
from vantage6.common import error, info
|
|
9
|
+
from vantage6.common.globals import STRING_ENCODING, APPNAME
|
|
10
|
+
from vantage6.common.docker.addons import remove_container, get_container
|
|
11
|
+
|
|
12
|
+
from vantage6.cli.globals import DEFAULT_SERVER_SYSTEM_FOLDERS
|
|
13
|
+
from vantage6.cli.context import ServerContext
|
|
14
|
+
from vantage6.cli.configuration_wizard import select_configuration_questionaire
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def click_insert_context(func: callable) -> callable:
|
|
18
|
+
"""
|
|
19
|
+
Supply the Click function with additional context parameters. The context
|
|
20
|
+
is then passed to the function as the first argument.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
func : Callable
|
|
25
|
+
function you want the context to be passed to
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
Callable
|
|
30
|
+
Click function with context
|
|
31
|
+
"""
|
|
32
|
+
@click.option('-n', '--name', default=None,
|
|
33
|
+
help="Name of the configuration you want to use.")
|
|
34
|
+
@click.option('-c', '--config', default=None,
|
|
35
|
+
help='Absolute path to configuration-file; overrides NAME')
|
|
36
|
+
@click.option('--system', 'system_folders', flag_value=True,
|
|
37
|
+
help='Use system folders instead of user folders. This is '
|
|
38
|
+
'the default')
|
|
39
|
+
@click.option('--user', 'system_folders', flag_value=False,
|
|
40
|
+
default=DEFAULT_SERVER_SYSTEM_FOLDERS,
|
|
41
|
+
help='Use user folders instead of system folders')
|
|
42
|
+
@wraps(func)
|
|
43
|
+
def func_with_context(name: str, config: str, system_folders: bool, *args,
|
|
44
|
+
**kwargs) -> callable:
|
|
45
|
+
"""
|
|
46
|
+
Decorator function that adds the context to the function.
|
|
47
|
+
|
|
48
|
+
Returns
|
|
49
|
+
-------
|
|
50
|
+
Callable
|
|
51
|
+
Decorated function
|
|
52
|
+
"""
|
|
53
|
+
# path to configuration file always overrides name
|
|
54
|
+
if config:
|
|
55
|
+
ctx = ServerContext.from_external_config_file(
|
|
56
|
+
config,
|
|
57
|
+
system_folders
|
|
58
|
+
)
|
|
59
|
+
elif 'ctx' in kwargs:
|
|
60
|
+
# if ctx is already in kwargs (typically when one click command
|
|
61
|
+
# calls another internally), use that existing ctx
|
|
62
|
+
ctx = kwargs.pop('ctx')
|
|
63
|
+
else:
|
|
64
|
+
# in case no name, ctx or config file is supplied, ask the user to
|
|
65
|
+
# select an existing config by name
|
|
66
|
+
if not name:
|
|
67
|
+
try:
|
|
68
|
+
# select configuration if none supplied
|
|
69
|
+
name = select_configuration_questionaire(
|
|
70
|
+
"server", system_folders
|
|
71
|
+
)
|
|
72
|
+
except Exception:
|
|
73
|
+
error("No configurations could be found!")
|
|
74
|
+
exit(1)
|
|
75
|
+
|
|
76
|
+
ctx = get_server_context(name, system_folders)
|
|
77
|
+
return func(ctx, *args, **kwargs)
|
|
78
|
+
|
|
79
|
+
return func_with_context
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def get_server_context(name: str, system_folders: bool) \
|
|
83
|
+
-> ServerContext:
|
|
84
|
+
"""
|
|
85
|
+
Load the server context from the configuration file.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
name : str
|
|
90
|
+
Name of the server to inspect
|
|
91
|
+
system_folders : bool
|
|
92
|
+
Wether to use system folders or if False, the user folders
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
ServerContext
|
|
97
|
+
Server context object
|
|
98
|
+
"""
|
|
99
|
+
if not ServerContext.config_exists(name, system_folders):
|
|
100
|
+
scope = "system" if system_folders else "user"
|
|
101
|
+
error(
|
|
102
|
+
f"Configuration {Fore.RED}{name}{Style.RESET_ALL} does not "
|
|
103
|
+
f"exist in the {Fore.RED}{scope}{Style.RESET_ALL} folders!"
|
|
104
|
+
)
|
|
105
|
+
exit(1)
|
|
106
|
+
|
|
107
|
+
# We do not want to log this here, we do this in the container and not on
|
|
108
|
+
# the host. We only want CLI logging here.
|
|
109
|
+
ServerContext.LOGGING_ENABLED = False
|
|
110
|
+
|
|
111
|
+
# create server context, and initialize db
|
|
112
|
+
ctx = ServerContext(name, system_folders=system_folders)
|
|
113
|
+
|
|
114
|
+
return ctx
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def print_log_worker(logs_stream: Iterable[bytes]) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Print the logs from the docker container to the terminal.
|
|
120
|
+
|
|
121
|
+
Parameters
|
|
122
|
+
----------
|
|
123
|
+
logs_stream : Iterable[bytes]
|
|
124
|
+
Output of the `container.attach(.)` method
|
|
125
|
+
"""
|
|
126
|
+
for log in logs_stream:
|
|
127
|
+
print(log.decode(STRING_ENCODING), end="")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def stop_ui(client: DockerClient, ctx: ServerContext) -> None:
|
|
131
|
+
"""
|
|
132
|
+
Check if the UI container is running, and if so, stop and remove it.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
client : DockerClient
|
|
137
|
+
Docker client
|
|
138
|
+
ctx : ServerContext
|
|
139
|
+
Server context object
|
|
140
|
+
"""
|
|
141
|
+
ui_container_name = f"{APPNAME}-{ctx.name}-{ctx.scope}-ui"
|
|
142
|
+
ui_container = get_container(client, name=ui_container_name)
|
|
143
|
+
if ui_container:
|
|
144
|
+
remove_container(ui_container, kill=True)
|
|
145
|
+
info(f"Stopped the {Fore.GREEN}{ui_container_name}"
|
|
146
|
+
f"{Style.RESET_ALL} User Interface container.")
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from vantage6.common import info
|
|
4
|
+
from vantage6.cli.context import ServerContext
|
|
5
|
+
from vantage6.cli.server.common import click_insert_context
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@click.command()
|
|
9
|
+
@click_insert_context
|
|
10
|
+
def cli_server_files(ctx: ServerContext) -> None:
|
|
11
|
+
"""
|
|
12
|
+
List files that belong to a particular server instance.
|
|
13
|
+
"""
|
|
14
|
+
info(f"Configuration file = {ctx.config_file}")
|
|
15
|
+
info(f"Log file = {ctx.log_file}")
|
|
16
|
+
info(f"Database = {ctx.get_database_uri()}")
|