vantage6 4.1.3__py3-none-any.whl → 4.2.0rc2__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.

@@ -214,7 +214,7 @@ class NodeCLITest(unittest.TestCase):
214
214
  log_dir=Path("logs"),
215
215
  config_dir=Path("configs"),
216
216
  databases=[{
217
- "label": "some-label",
217
+ "label": "some_label",
218
218
  "uri": "data.csv",
219
219
  "type": "csv"
220
220
  }]
@@ -126,23 +126,14 @@ class ServerCLITest(unittest.TestCase):
126
126
  self.assertIsNone(result.exception)
127
127
  self.assertEqual(result.exit_code, 0)
128
128
 
129
- @patch("vantage6.cli.server.stop.ServerContext")
130
129
  @patch("vantage6.cli.server.stop.docker.from_env")
131
- @patch("vantage6.cli.server.stop.check_docker_running", return_value=True)
132
- def test_stop(self, docker_check, containers, context):
130
+ def test_stop(self, containers):
133
131
  """Stop server without errors."""
134
132
 
135
133
  container1 = MagicMock()
136
134
  container1.name = f"{APPNAME}-iknl-system-server"
137
135
  containers.containers.list.return_value = [container1]
138
136
 
139
- ctx = MagicMock(
140
- config={
141
- 'rabbitmq_uri': None
142
- }
143
- )
144
- context.return_value = ctx
145
-
146
137
  runner = CliRunner()
147
138
  result = runner.invoke(cli_server_stop, ["--name", "iknl"])
148
139
 
@@ -151,9 +142,7 @@ class ServerCLITest(unittest.TestCase):
151
142
 
152
143
  @patch("vantage6.cli.server.attach.time.sleep")
153
144
  @patch("docker.DockerClient.containers")
154
- @patch("vantage6.cli.server.attach.check_docker_running",
155
- return_value=True)
156
- def test_attach(self, docker_check, containers, sleep):
145
+ def test_attach(self, containers, sleep):
157
146
  """Attach log to the console without errors."""
158
147
  container1 = MagicMock()
159
148
  container1.name = f"{APPNAME}-iknl-system-server"
vantage6/cli/__build__ CHANGED
@@ -1 +1 @@
1
- 0
1
+ 2
vantage6/cli/_version.py CHANGED
@@ -7,7 +7,7 @@ with open(os.path.join(here, '__build__')) as fp:
7
7
  __build__ = json.load(fp)
8
8
 
9
9
  # Module version
10
- version_info = (4, 1, 3, 'final', __build__, 0)
10
+ version_info = (4, 2, 0, 'candidate', __build__, 0)
11
11
 
12
12
  # Module version stage suffix map
13
13
  _specifier_ = {'alpha': 'a', 'beta': 'b', 'candidate': 'rc', 'final': ''}
vantage6/cli/cli.py CHANGED
@@ -5,6 +5,7 @@ from vantage6.cli.server.files import cli_server_files
5
5
  from vantage6.cli.server.import_ import cli_server_import
6
6
  from vantage6.cli.server.list import cli_server_configuration_list
7
7
  from vantage6.cli.server.new import cli_server_new
8
+ from vantage6.cli.server.remove import cli_server_remove
8
9
  from vantage6.cli.server.shell import cli_server_shell
9
10
  from vantage6.cli.server.start import cli_server_start
10
11
  from vantage6.cli.server.stop import cli_server_stop
@@ -26,6 +27,8 @@ from vantage6.cli.dev.start import start_demo_network
26
27
  from vantage6.cli.dev.stop import stop_demo_network
27
28
  from vantage6.cli.algorithm.create import cli_algorithm_create
28
29
  from vantage6.cli.algorithm.update import cli_algorithm_update
30
+ from vantage6.cli.test.feature_tester import cli_test_features
31
+ from vantage6.cli.test.integration_test import cli_test_integration
29
32
 
30
33
 
31
34
  # Define the server group
@@ -42,6 +45,7 @@ cli_server.add_command(cli_server_files, name='files')
42
45
  cli_server.add_command(cli_server_import, name='import')
43
46
  cli_server.add_command(cli_server_configuration_list, name='list')
44
47
  cli_server.add_command(cli_server_new, name='new')
48
+ cli_server.add_command(cli_server_remove, name='remove')
45
49
  cli_server.add_command(cli_server_shell, name='shell')
46
50
  cli_server.add_command(cli_server_start, name='start')
47
51
  cli_server.add_command(cli_server_stop, name='stop')
@@ -100,6 +104,19 @@ cli_algorithm.add_command(cli_algorithm_create, name="create")
100
104
  cli_algorithm.add_command(cli_algorithm_update, name="update")
101
105
 
102
106
 
107
+ # Define the test group
108
+ @click.group(name="test")
109
+ def cli_test() -> None:
110
+ """
111
+ Execute tests on your vantage6 infrastructure.
112
+ """
113
+
114
+
115
+ # Define the commands for the test group
116
+ cli_test.add_command(cli_test_features, name="feature-test")
117
+ cli_test.add_command(cli_test_integration, name="integration-test")
118
+
119
+
103
120
  # Define the overall group
104
121
  @click.group(name='cli')
105
122
  def cli_complete() -> None:
@@ -116,3 +133,4 @@ cli_complete.add_command(cli_node)
116
133
  cli_complete.add_command(cli_server)
117
134
  cli_complete.add_command(cli_dev)
118
135
  cli_complete.add_command(cli_algorithm)
136
+ cli_complete.add_command(cli_test)
@@ -69,7 +69,8 @@ def create_node_config_file(server_url: str, port: int, config: dict,
69
69
  port : int
70
70
  Port of the dummy server.
71
71
  config : dict
72
- Configuration dictionary containing org_id, api_key and node name.
72
+ Configuration dictionary containing org_id, api_key, node name and
73
+ additional user_defined_config.
73
74
  server_name : str
74
75
  Configuration name of the dummy server.
75
76
  """
@@ -103,7 +104,8 @@ def create_node_config_file(server_url: str, port: int, config: dict,
103
104
  },
104
105
  "port": port,
105
106
  "server_url": server_url,
106
- "task_dir": str(path_to_data_dir)
107
+ "task_dir": str(path_to_data_dir),
108
+ "user_provided_config": config['user_defined_config']
107
109
  })
108
110
 
109
111
  try:
@@ -117,9 +119,31 @@ def create_node_config_file(server_url: str, port: int, config: dict,
117
119
  f"{Style.RESET_ALL}")
118
120
 
119
121
 
120
- def generate_node_configs(num_nodes: int, server_url: str, port: int,
121
- server_name: str) \
122
- -> list[dict]:
122
+ def _read_extra_config_file(extra_config_file: Path | None) -> str:
123
+ """Reads extra configuration file.
124
+
125
+ Parameters
126
+ ----------
127
+ extra_config_file : Path | None
128
+ Path to file with additional configuration.
129
+
130
+ Returns
131
+ -------
132
+ str
133
+ Extra configuration file content
134
+ """
135
+ if extra_config_file:
136
+ # read the YAML file as string, so it can be appended to the
137
+ # configuration easily
138
+ with open(extra_config_file, 'r', encoding='utf-8') as f:
139
+ return f.read()
140
+ return ''
141
+
142
+
143
+ def generate_node_configs(
144
+ num_nodes: int, server_url: str, port: int, server_name: str,
145
+ extra_node_config: Path | None
146
+ ) -> list[dict]:
123
147
  """Generates ``num_nodes`` node configuration files.
124
148
 
125
149
  Parameters
@@ -132,6 +156,8 @@ def generate_node_configs(num_nodes: int, server_url: str, port: int,
132
156
  Port of the dummy server.
133
157
  server_name : str
134
158
  Configuration name of the dummy server.
159
+ extra_node_config : Path | None
160
+ Path to file with additional node configuration.
135
161
 
136
162
  Returns
137
163
  -------
@@ -139,11 +165,13 @@ def generate_node_configs(num_nodes: int, server_url: str, port: int,
139
165
  List of dictionaries containing node configurations.
140
166
  """
141
167
  configs = []
168
+ extra_config = _read_extra_config_file(extra_node_config)
142
169
  for i in range(num_nodes):
143
170
  config = {
144
171
  'org_id': i + 1,
145
172
  'api_key': generate_apikey(),
146
- 'node_name': f"{server_name}_node_{i + 1}"
173
+ 'node_name': f"{server_name}_node_{i + 1}",
174
+ "user_defined_config": extra_config
147
175
  }
148
176
  create_node_config_file(server_url, port, config, server_name)
149
177
  configs.append(config)
@@ -211,7 +239,9 @@ def create_vserver_import_config(node_configs: list[dict], server_name: str) \
211
239
  return full_path
212
240
 
213
241
 
214
- def create_vserver_config(server_name: str, port: int) -> Path:
242
+ def create_vserver_config(
243
+ server_name: str, port: int, extra_config_file: Path
244
+ ) -> Path:
215
245
  """Creates server configuration file (YAML).
216
246
 
217
247
  Parameters
@@ -220,6 +250,8 @@ def create_vserver_config(server_name: str, port: int) -> Path:
220
250
  Server name.
221
251
  port : int
222
252
  Server port.
253
+ extra_config_file : Path
254
+ Path to file with additional server configuration.
223
255
 
224
256
  Returns
225
257
  -------
@@ -229,10 +261,14 @@ def create_vserver_config(server_name: str, port: int) -> Path:
229
261
  environment = Environment(
230
262
  loader=FileSystemLoader(PACKAGE_FOLDER / APPNAME / "cli" / "template"),
231
263
  trim_blocks=True, lstrip_blocks=True, autoescape=True)
264
+
265
+ extra_config = _read_extra_config_file(extra_config_file)
266
+
232
267
  template = environment.get_template("server_config.j2")
233
268
  server_config = template.render(
234
269
  port=port,
235
- jwt_secret_key=generate_apikey()
270
+ jwt_secret_key=generate_apikey(),
271
+ user_provided_config=extra_config
236
272
  )
237
273
  folders = ServerContext.instance_folders(
238
274
  instance_type='server', instance_name=server_name,
@@ -257,8 +293,10 @@ def create_vserver_config(server_name: str, port: int) -> Path:
257
293
  return full_path
258
294
 
259
295
 
260
- def demo_network(num_nodes: int, server_url: str, server_port: int,
261
- server_name: str) -> tuple[list[dict], Path, Path]:
296
+ def demo_network(
297
+ num_nodes: int, server_url: str, server_port: int, server_name: str,
298
+ extra_server_config: Path, extra_node_config: Path
299
+ ) -> tuple[list[dict], Path, Path]:
262
300
  """Generates the demo network.
263
301
 
264
302
  Parameters
@@ -271,6 +309,10 @@ def demo_network(num_nodes: int, server_url: str, server_port: int,
271
309
  Port of the dummy server.
272
310
  server_name : str
273
311
  Server name.
312
+ extra_server_config : Path
313
+ Path to file with additional server configuration.
314
+ extra_node_config : Path
315
+ Path to file with additional node configuration.
274
316
 
275
317
  Returns
276
318
  -------
@@ -278,10 +320,11 @@ def demo_network(num_nodes: int, server_url: str, server_port: int,
278
320
  Tuple containing node, server import and server configurations.
279
321
  """
280
322
  node_configs = generate_node_configs(num_nodes, server_url, server_port,
281
- server_name)
323
+ server_name, extra_node_config)
282
324
  server_import_config = create_vserver_import_config(node_configs,
283
325
  server_name)
284
- server_config = create_vserver_config(server_name, server_port)
326
+ server_config = create_vserver_config(server_name, server_port,
327
+ extra_server_config)
285
328
  return (node_configs, server_import_config, server_config)
286
329
 
287
330
 
@@ -298,10 +341,18 @@ def demo_network(num_nodes: int, server_url: str, server_port: int,
298
341
  @click.option('-i', '--image', type=str, default=None,
299
342
  help='Server docker image to use when setting up resources for '
300
343
  'the development server')
344
+ @click.option('--extra-server-config', type=click.Path(exists=True),
345
+ default=None, help='YAML File with additional server '
346
+ 'configuration. This will be appended to the server '
347
+ 'configuration file')
348
+ @click.option('--extra-node-config', type=click.Path('rb'), default=None,
349
+ help='YAML File with additional node configuration. This will be'
350
+ ' appended to each of the node configuration files')
301
351
  @click.pass_context
302
352
  def create_demo_network(
303
353
  click_ctx: click.Context, name: str, num_nodes: int, server_url: str,
304
- server_port: int, image: str = None
354
+ server_port: int, image: str = None, extra_server_config: Path = None,
355
+ extra_node_config: Path = None
305
356
  ) -> dict:
306
357
  """Creates a demo network.
307
358
 
@@ -312,7 +363,10 @@ def create_demo_network(
312
363
  """
313
364
  server_name = prompt_config_name(name)
314
365
  if not ServerContext.config_exists(server_name):
315
- demo = demo_network(num_nodes, server_url, server_port, server_name)
366
+ demo = demo_network(
367
+ num_nodes, server_url, server_port, server_name,
368
+ extra_server_config, extra_node_config
369
+ )
316
370
  info(f"Created {Fore.GREEN}{len(demo[0])}{Style.RESET_ALL} node "
317
371
  f"configuration(s), attaching them to {Fore.GREEN}{server_name}"
318
372
  f"{Style.RESET_ALL}.")
@@ -8,15 +8,16 @@ import click
8
8
  from vantage6.common import info
9
9
  from vantage6.cli.context import ServerContext, NodeContext
10
10
  from vantage6.cli.server.common import click_insert_context
11
- from vantage6.cli.server.stop import vserver_remove
11
+ from vantage6.cli.server.remove import cli_server_remove
12
12
  from vantage6.cli.utils import remove_file
13
13
 
14
14
 
15
15
  @click.command()
16
16
  @click_insert_context
17
- @click.option('-f', "--force", type=bool, flag_value=True,
18
- help='Don\'t ask for confirmation')
19
- def remove_demo_network(ctx: ServerContext, force: bool) -> None:
17
+ @click.pass_context
18
+ def remove_demo_network(
19
+ click_ctx: click.Context, ctx: ServerContext
20
+ ) -> None:
20
21
  """ Remove all related demo network files and folders.
21
22
 
22
23
  Select a server configuration to remove that server and the nodes attached
@@ -26,7 +27,7 @@ def remove_demo_network(ctx: ServerContext, force: bool) -> None:
26
27
  # remove the server
27
28
  for handler in itertools.chain(ctx.log.handlers, ctx.log.root.handlers):
28
29
  handler.close()
29
- vserver_remove(ctx, ctx.name, True, force)
30
+ click_ctx.invoke(cli_server_remove, ctx=ctx, force=True)
30
31
 
31
32
  # removing the server import config
32
33
  info("Deleting demo import config file")
@@ -41,8 +42,6 @@ def remove_demo_network(ctx: ServerContext, force: bool) -> None:
41
42
  server_folder = server_configs['data']
42
43
  if server_folder.is_dir():
43
44
  rmtree(server_folder)
44
- # TODO BvB 2023-07-31 can it happen that the server folder is not a
45
- # directory? What then?
46
45
 
47
46
  # remove the nodes
48
47
  configs, _ = NodeContext.available_configurations(system_folders=False)
vantage6/cli/globals.py CHANGED
@@ -36,3 +36,6 @@ DEFAULT_UI_PORT = 5001
36
36
 
37
37
  # Location of repository to create new algorithm templates from
38
38
  ALGORITHM_TEMPLATE_REPO = "gh:vantage6/v6-algorithm-template.git"
39
+
40
+ # image to use for diagnostics in `v6 test` commands
41
+ DIAGNOSTICS_IMAGE = "harbor2.vantage6.ai/algorithms/diagnostic"
@@ -10,10 +10,7 @@ import docker
10
10
 
11
11
  from colorama import Fore, Style
12
12
 
13
- from vantage6.common import (
14
- warning, error, info, debug,
15
- get_database_config
16
- )
13
+ from vantage6.common import warning, error, info, debug, get_database_config
17
14
  from vantage6.common.globals import (
18
15
  APPNAME,
19
16
  DEFAULT_DOCKER_REGISTRY,
@@ -21,15 +18,13 @@ from vantage6.common.globals import (
21
18
  DEFAULT_NODE_IMAGE_WO_TAG,
22
19
  )
23
20
  from vantage6.common.docker.addons import (
24
- pull_if_newer,
25
- remove_container_if_exists,
26
- check_docker_running
21
+ pull_if_newer,
22
+ remove_container_if_exists,
23
+ check_docker_running,
27
24
  )
28
25
 
29
26
  from vantage6.cli.context import NodeContext
30
- from vantage6.cli.globals import (
31
- DEFAULT_NODE_SYSTEM_FOLDERS as N_FOL
32
- )
27
+ from vantage6.cli.globals import DEFAULT_NODE_SYSTEM_FOLDERS as N_FOL
33
28
  from vantage6.cli.configuration_wizard import (
34
29
  configuration_wizard,
35
30
  select_configuration_questionaire,
@@ -41,28 +36,54 @@ from vantage6.cli.node.common import print_log_worker, create_client
41
36
 
42
37
  @click.command()
43
38
  @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:
39
+ @click.option(
40
+ "-c", "--config", default=None, help="Path to configuration-file; overrides NAME"
41
+ )
42
+ @click.option(
43
+ "--system",
44
+ "system_folders",
45
+ flag_value=True,
46
+ help="Search for the configuration in the system folders",
47
+ )
48
+ @click.option(
49
+ "--user",
50
+ "system_folders",
51
+ flag_value=False,
52
+ default=N_FOL,
53
+ help="Search for the configuration in the user folders. This is " "the default",
54
+ )
55
+ @click.option("-i", "--image", default=None, help="Node Docker image to use")
56
+ @click.option(
57
+ "--keep/--auto-remove",
58
+ default=False,
59
+ help="Keep node container after finishing. Useful for debugging",
60
+ )
61
+ @click.option(
62
+ "--force-db-mount",
63
+ is_flag=True,
64
+ help="Always mount node databases; skip the check if they are " "existing files.",
65
+ )
66
+ @click.option(
67
+ "--attach/--detach",
68
+ default=False,
69
+ help="Show node logs on the current console after starting the " "node",
70
+ )
71
+ @click.option(
72
+ "--mount-src",
73
+ default="",
74
+ help="Override vantage6 source code in container with the source"
75
+ " code in this path",
76
+ )
77
+ def cli_node_start(
78
+ name: str,
79
+ config: str,
80
+ system_folders: bool,
81
+ image: str,
82
+ keep: bool,
83
+ mount_src: str,
84
+ attach: bool,
85
+ force_db_mount: bool,
86
+ ) -> None:
66
87
  """
67
88
  Start the node.
68
89
  """
@@ -82,8 +103,7 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
82
103
 
83
104
  # check that config exists, if not a questionaire will be invoked
84
105
  if not NodeContext.config_exists(name, system_folders):
85
- warning(f"Configuration {Fore.RED}{name}{Style.RESET_ALL} does not"
86
- " exist.")
106
+ warning(f"Configuration {Fore.RED}{name}{Style.RESET_ALL} does not exist.")
87
107
 
88
108
  if q.confirm("Create this configuration now?").ask():
89
109
  configuration_wizard("node", name, system_folders)
@@ -117,7 +137,7 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
117
137
  # Then we check if the image has been specified in the config file, and
118
138
  # finally we use the default settings from the package.
119
139
  if not image:
120
- custom_images: dict = ctx.config.get('images')
140
+ custom_images: dict = ctx.config.get("images")
121
141
  if custom_images:
122
142
  image = custom_images.get("node")
123
143
  else:
@@ -127,16 +147,19 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
127
147
  major_minor = None
128
148
  try:
129
149
  # 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}")
150
+ version = client.util.get_server_version(attempts_on_timeout=3)[
151
+ "version"
152
+ ]
153
+ major_minor = ".".join(version.split(".")[:2])
154
+ image = (
155
+ f"{DEFAULT_DOCKER_REGISTRY}/"
156
+ f"{DEFAULT_NODE_IMAGE_WO_TAG}"
157
+ f":{major_minor}"
158
+ )
137
159
  except Exception:
138
- warning("Could not determine server version. Using default "
139
- "node image")
160
+ warning(
161
+ "Could not determine server version. Using default " "node image"
162
+ )
140
163
 
141
164
  if major_minor and not __version__.startswith(major_minor):
142
165
  warning(
@@ -158,7 +181,7 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
158
181
  pull_if_newer(docker.from_env(), image)
159
182
 
160
183
  except Exception as e:
161
- warning(' ... Getting latest node image failed:')
184
+ warning(" ... Getting latest node image failed:")
162
185
  warning(f" {e}")
163
186
  else:
164
187
  info(" ... success!")
@@ -186,7 +209,7 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
186
209
  if mount_src:
187
210
  # If mount_src is a relative path, docker will consider it a volume.
188
211
  mount_src = os.path.abspath(mount_src)
189
- mounts.append(('/vantage6', mount_src))
212
+ mounts.append(("/vantage6", mount_src))
190
213
 
191
214
  # FIXME: Code duplication: Node.__init__() (vantage6/node/__init__.py)
192
215
  # uses a lot of the same logic. Suggest moving this to
@@ -194,10 +217,10 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
194
217
  filename = ctx.config.get("encryption", {}).get("private_key")
195
218
  # filename may be set to an empty string
196
219
  if not filename:
197
- filename = 'private_key.pem'
220
+ filename = "private_key.pem"
198
221
 
199
222
  # Location may be overridden by the environment
200
- filename = os.environ.get('PRIVATE_KEY', filename)
223
+ filename = os.environ.get("PRIVATE_KEY", filename)
201
224
 
202
225
  # If ctx.get_data_file() receives an absolute path, it is returned as-is
203
226
  fullpath = Path(ctx.get_data_file(filename))
@@ -205,8 +228,7 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
205
228
  if Path(fullpath).exists():
206
229
  mounts.append(("/mnt/private_key.pem", str(fullpath)))
207
230
  else:
208
- warning(f"private key file provided {fullpath}, "
209
- "but does not exists")
231
+ warning(f"private key file provided {fullpath}, " "but does not exists")
210
232
 
211
233
  # Mount private keys for ssh tunnels
212
234
  ssh_tunnels = ctx.config.get("ssh-tunnels", [])
@@ -214,13 +236,17 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
214
236
  hostname = ssh_tunnel.get("hostname")
215
237
  key_path = ssh_tunnel.get("ssh", {}).get("identity", {}).get("key")
216
238
  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.")
239
+ error(
240
+ f"SSH tunnel identity {Fore.RED}{hostname}{Style.RESET_ALL} "
241
+ "key not provided. Continuing to start without this tunnel."
242
+ )
219
243
  key_path = Path(key_path)
220
244
  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.")
245
+ error(
246
+ f"SSH tunnel identity {Fore.RED}{hostname}{Style.RESET_ALL} "
247
+ "key does not exist. Continuing to start without this "
248
+ "tunnel."
249
+ )
224
250
 
225
251
  info(f" Mounting private key for {hostname} at {key_path}")
226
252
 
@@ -233,20 +259,29 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
233
259
  env = {
234
260
  "DATA_VOLUME_NAME": data_volume.name,
235
261
  "VPN_VOLUME_NAME": vpn_volume.name,
236
- "PRIVATE_KEY": "/mnt/private_key.pem"
262
+ "PRIVATE_KEY": "/mnt/private_key.pem",
237
263
  }
238
264
 
239
265
  # only mount the DB if it is a file
240
266
  info("Setting up databases")
241
- db_labels = [db['label'] for db in ctx.databases]
267
+ db_labels = [db["label"] for db in ctx.databases]
242
268
  for label in db_labels:
243
269
 
270
+ # check that label contains only valid characters
271
+ if not label.isidentifier():
272
+ error(f"Database label {Fore.RED}{label}{Style.RESET_ALL} contains"
273
+ " invalid characters. Only letters, numbers, and underscores"
274
+ " are allowed, and it cannot start with a number.")
275
+ exit(1)
276
+
244
277
  db_config = get_database_config(ctx.databases, label)
245
- uri = db_config['uri']
246
- db_type = db_config['type']
278
+ uri = db_config["uri"]
279
+ db_type = db_config["type"]
247
280
 
248
- info(f" Processing {Fore.GREEN}{db_type}{Style.RESET_ALL} database "
249
- f"{Fore.GREEN}{label}:{uri}{Style.RESET_ALL}")
281
+ info(
282
+ f" Processing {Fore.GREEN}{db_type}{Style.RESET_ALL} database "
283
+ f"{Fore.GREEN}{label}:{uri}{Style.RESET_ALL}"
284
+ )
250
285
  label_capitals = label.upper()
251
286
 
252
287
  try:
@@ -259,22 +294,24 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
259
294
  file_based = False
260
295
 
261
296
  if not file_based and not force_db_mount:
262
- debug(' - non file-based database added')
263
- env[f'{label_capitals}_DATABASE_URI'] = uri
297
+ debug(" - non file-based database added")
298
+ env[f"{label_capitals}_DATABASE_URI"] = uri
264
299
  else:
265
- debug(' - file-based database added')
300
+ debug(" - file-based database added")
266
301
  suffix = Path(uri).suffix
267
- env[f'{label_capitals}_DATABASE_URI'] = f'{label}{suffix}'
268
- mounts.append((f'/mnt/{label}{suffix}', str(uri)))
302
+ env[f"{label_capitals}_DATABASE_URI"] = f"{label}{suffix}"
303
+ mounts.append((f"/mnt/{label}{suffix}", str(uri)))
269
304
 
270
305
  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}'
306
+ cmd = (
307
+ f"vnode-local start -c /mnt/config/{name}.yaml -n {name} "
308
+ f" --dockerized {system_folders_option}"
309
+ )
273
310
 
274
311
  info("Running Docker container")
275
312
  volumes = []
276
313
  for mount in mounts:
277
- volumes.append(f'{mount[1]}:{mount[0]}')
314
+ volumes.append(f"{mount[1]}:{mount[0]}")
278
315
 
279
316
  remove_container_if_exists(
280
317
  docker_client=docker_client, name=ctx.docker_container_name
@@ -288,15 +325,15 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
288
325
  labels={
289
326
  f"{APPNAME}-type": "node",
290
327
  "system": str(system_folders),
291
- "name": ctx.config_file_name
328
+ "name": ctx.config_file_name,
292
329
  },
293
330
  environment=env,
294
331
  name=ctx.docker_container_name,
295
332
  auto_remove=not keep,
296
- tty=True
333
+ tty=True,
297
334
  )
298
335
 
299
- info(f"Success! container id = {container}")
336
+ info("Node container was successfully started!")
300
337
 
301
338
  if attach:
302
339
  logs = container.attach(stream=True, logs=True)
@@ -306,6 +343,11 @@ def cli_node_start(name: str, config: str, system_folders: bool, image: str,
306
343
  time.sleep(1)
307
344
  except KeyboardInterrupt:
308
345
  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}'")
346
+ info(
347
+ "Note that your node is still running! Shut it down with "
348
+ f"'{Fore.RED}v6 node stop{Style.RESET_ALL}'"
349
+ )
311
350
  exit(0)
351
+ else:
352
+ info(f"To see the logs, run: {Fore.GREEN}v6 node attach --name "
353
+ f"{ctx.name}{Style.RESET_ALL}")
@@ -0,0 +1,41 @@
1
+ import itertools
2
+ import click
3
+ import questionary as q
4
+
5
+ from vantage6.common import info
6
+ from vantage6.common.docker.addons import check_docker_running
7
+ from vantage6.cli.server.common import click_insert_context
8
+ from vantage6.cli.context import ServerContext
9
+ from vantage6.cli.utils import remove_file
10
+
11
+
12
+ @click.command()
13
+ @click_insert_context
14
+ @click.option('-f', '--force', 'force', flag_value=True)
15
+ def cli_server_remove(ctx: ServerContext, force: bool) -> None:
16
+ """
17
+ Function to remove a server.
18
+
19
+ Parameters
20
+ ----------
21
+ ctx : ServerContext
22
+ Server context object
23
+ force : bool
24
+ Whether to ask for confirmation before removing or not
25
+ """
26
+ check_docker_running()
27
+
28
+ if not force:
29
+ if not q.confirm(
30
+ "This server will be deleted permanently including its "
31
+ "configuration. Are you sure?", default=False
32
+ ).ask():
33
+ info("Server will not be deleted")
34
+ exit(0)
35
+
36
+ # now remove the folders...
37
+ remove_file(ctx.config_file, 'configuration')
38
+
39
+ for handler in itertools.chain(ctx.log.handlers, ctx.log.root.handlers):
40
+ handler.close()
41
+ remove_file(ctx.log_file, 'log')
@@ -1,5 +1,3 @@
1
- import itertools
2
-
3
1
  import click
4
2
  import questionary as q
5
3
  import docker
@@ -16,8 +14,6 @@ from vantage6.common.globals import APPNAME
16
14
  from vantage6.cli.rabbitmq import split_rabbitmq_uri
17
15
 
18
16
  from vantage6.cli.globals import DEFAULT_SERVER_SYSTEM_FOLDERS
19
- from vantage6.cli.context import ServerContext
20
- from vantage6.cli.utils import remove_file
21
17
  from vantage6.cli.server.common import get_server_context, stop_ui
22
18
 
23
19
 
@@ -109,50 +105,3 @@ def _stop_server_containers(client: DockerClient, container_name: str,
109
105
  remove_container(rabbit_container, kill=True)
110
106
  info(f"Stopped the {Fore.GREEN}{rabbit_container_name}"
111
107
  f"{Style.RESET_ALL} container.")
112
-
113
-
114
- # TODO this should be refactored into its own module and get a click command
115
- # attached
116
- @click.pass_context
117
- def vserver_remove(
118
- click_ctx: click.Context, ctx: ServerContext, name: str,
119
- system_folders: bool, force: bool
120
- ) -> None:
121
- """
122
- Function to remove a server.
123
-
124
- Parameters
125
- ----------
126
- ctx : ServerContext
127
- Server context object
128
- name : str
129
- Name of the server to remove
130
- system_folders : bool
131
- Whether to use system folders or not
132
- force : bool
133
- Whether to ask for confirmation before removing or not
134
- """
135
- check_docker_running()
136
-
137
- # first stop server
138
- click_ctx.invoke(
139
- cli_server_stop, name=name, system_folders=system_folders,
140
- all_servers=False
141
- )
142
-
143
- if not force:
144
- if not q.confirm(
145
- "This server will be deleted permanently including its "
146
- "configuration. Are you sure?", default=False
147
- ).ask():
148
- info("Server will not be deleted")
149
- exit(0)
150
-
151
- # now remove the folders...
152
- info(f"Removing configuration file {ctx.config_file}")
153
- remove_file(ctx.config_file, 'configuration')
154
-
155
- info(f"Removing log file {ctx.log_file}")
156
- for handler in itertools.chain(ctx.log.handlers, ctx.log.root.handlers):
157
- handler.close()
158
- remove_file(ctx.log_file, 'log')
@@ -20,3 +20,4 @@ logging:
20
20
  port: {{ port }}
21
21
  server_url: {{ server_url }}
22
22
  task_dir: {{ task_dir}}
23
+ {{ user_provided_config }}
@@ -12,4 +12,5 @@ logging:
12
12
  use_console: true
13
13
  port: {{ port }}
14
14
  uri: sqlite:///default.sqlite
15
- jwt_secret_key: {{ jwt_secret_key }}
15
+ jwt_secret_key: {{ jwt_secret_key }}
16
+ {{ user_provided_config }}
@@ -0,0 +1,200 @@
1
+ import json
2
+
3
+ from typing import Any
4
+ from rich.console import Console
5
+ from rich.table import Table
6
+
7
+ from vantage6.client import UserClient
8
+ from vantage6.common import info, debug
9
+ from vantage6.cli.globals import DIAGNOSTICS_IMAGE
10
+
11
+
12
+ class DiagnosticRunner:
13
+ """
14
+ Class to run the diagnostic algorithm on a vantage6 network.
15
+
16
+ This class will create a task in the requested collaboration that will test
17
+ the functionality of vantage6, and will report back the results.
18
+
19
+ Parameters
20
+ ----------
21
+ client : UserClient
22
+ The client to use for communication with the server.
23
+ collaboration_id : int
24
+ The ID of the collaboration to run the diagnostics in.
25
+ organizations : list[int] | None
26
+ The ID(s) of the organization(s) to run the diagnostics for. If None
27
+ (default), run the diagnostics for all organizations in the
28
+ collaboration.
29
+ online_only : bool
30
+ Whether to run the diagnostics only on nodes that are online. By
31
+ default False
32
+ """
33
+ def __init__(
34
+ self, client: UserClient, collaboration_id: int,
35
+ organizations: list[int] | None = None, online_only: bool = False
36
+ ) -> None:
37
+ self.client = client
38
+ self.collaboration_id = collaboration_id
39
+
40
+ if not organizations:
41
+ # run on all organizations in the collaboration
42
+ # TODO use pagination properly, instead of just getting the first
43
+ # 1000 organizations (which very likely is enough though)
44
+ orgs = self.client.organization.list(
45
+ collaboration=self.collaboration_id, per_page=1000
46
+ )
47
+ self.organization_ids = [org['id'] for org in orgs['data']]
48
+ else:
49
+ self.organization_ids = organizations
50
+
51
+ if online_only:
52
+ nodes = self.client.node.list(
53
+ collaboration=self.collaboration_id,
54
+ is_online=True
55
+ )
56
+ debug(nodes)
57
+ online_orgs = [
58
+ node['organization']['id'] for node in nodes['data']
59
+ ]
60
+ self.organization_ids = \
61
+ list(set(self.organization_ids).intersection(online_orgs))
62
+
63
+ info(f"Running diagnostics to {len(self.organization_ids)} "
64
+ "organization(s)")
65
+ info(f" organizations: {self.organization_ids}")
66
+ info(f" collaboration: {self.collaboration_id}")
67
+
68
+ def __call__(self, base: bool = True, vpn: bool = True) -> Any:
69
+ """
70
+ Run the diagnostics.
71
+
72
+ Parameters
73
+ ----------
74
+ base: bool
75
+ Whether to run the base features of the diagnostic algorithm. By
76
+ default True.
77
+ vpn: bool
78
+ Whether to run the VPN features of the diagnostic algorithm. By
79
+ default True.
80
+ """
81
+ base_results = self.base_features() if base else []
82
+ vpn_results = self.vpn_features() if vpn else []
83
+ return base_results + vpn_results
84
+
85
+ def base_features(self) -> list[dict]:
86
+ """
87
+ Create a task to run the base features of the diagnostic algorithm.
88
+
89
+ Returns
90
+ -------
91
+ list[dict]
92
+ The results of the diagnostic algorithm.
93
+ """
94
+ task = self.client.task.create(
95
+ collaboration=self.collaboration_id,
96
+ name="test",
97
+ description="Basic Diagnostic test",
98
+ image=DIAGNOSTICS_IMAGE,
99
+ input_={
100
+ "method": "base_features",
101
+ },
102
+ organizations=self.organization_ids,
103
+ databases=[
104
+ {'label': 'default'}
105
+ ]
106
+ )
107
+ debug(task)
108
+
109
+ return self._wait_and_display(task.get("id"))
110
+
111
+ def vpn_features(self) -> list[dict]:
112
+ """
113
+ Create a task to run the VPN features of the diagnostic algorithm.
114
+
115
+ Returns
116
+ -------
117
+ list[dict]
118
+ The results of the diagnostic algorithm.
119
+ """
120
+ self.client.node.list(collaboration=self.collaboration_id)
121
+
122
+ task = self.client.task.create(
123
+ collaboration=self.collaboration_id,
124
+ name="test",
125
+ description="VPN Diagnostic test",
126
+ image=DIAGNOSTICS_IMAGE,
127
+ input_={
128
+ "method": "vpn_features",
129
+ "kwargs": {"other_nodes": self.organization_ids}
130
+ },
131
+ organizations=self.organization_ids,
132
+ )
133
+
134
+ return self._wait_and_display(task.get("id"))
135
+
136
+ def _wait_and_display(self, task_id: int) -> list[dict]:
137
+ """
138
+ Wait for the task to finish and then display the results.
139
+
140
+ Parameters
141
+ ----------
142
+ task_id : int
143
+ The ID of the task to wait for.
144
+
145
+ Returns
146
+ -------
147
+ list[dict]
148
+ The results of the diagnostic algorithm.
149
+ """
150
+ # TODO should we have the option to combine these in one request? Seems
151
+ # like it would be more efficient
152
+ # TODO ensure that we get all pages of results
153
+ results = self.client.wait_for_results(task_id=task_id)['data']
154
+ runs = self.client.run.from_task(task_id=task_id)['data']
155
+ print("\n")
156
+ for res in results:
157
+ matched_run = [
158
+ run for run in runs if run['id'] == res['run']['id']
159
+ ][0]
160
+ self.display_diagnostic_results(
161
+ res, matched_run['organization']['id']
162
+ )
163
+ print()
164
+ return results
165
+
166
+ def display_diagnostic_results(self, result: dict, org_id: int) -> None:
167
+ """
168
+ Print the results of the diagnostic algorithm.
169
+
170
+ Parameters
171
+ ----------
172
+ result : list[dict]
173
+ The result of the diagnostic algorithm.
174
+ org_id : int
175
+ The ID of the organization that the diagnostic algorithm was run on
176
+ """
177
+ res = json.loads(result["result"])
178
+ t_ = Table(title=f"Basic Diagnostics Summary (organization {org_id})")
179
+ t_.add_column('name')
180
+ t_.add_column('success')
181
+ e_ = Table(title=f"Basic Diagnostics Errors (organization {org_id})")
182
+ e_.add_column('name')
183
+ e_.add_column('exception')
184
+ e_.add_column('traceback')
185
+ e_.add_column('payload')
186
+ errors = False
187
+ for diag in res:
188
+ if diag['success']:
189
+ success = ":heavy_check_mark: [green]success[/green]"
190
+ else:
191
+ success = ":x: [red]failed[/red]"
192
+ e_.add_row(diag["name"], diag["exception"], diag["traceback"],
193
+ diag["payload"])
194
+ errors = True
195
+ t_.add_row(diag["name"], success)
196
+
197
+ console = Console()
198
+ console.print(t_)
199
+ if errors:
200
+ console.print(e_)
@@ -0,0 +1,54 @@
1
+ import sys
2
+ import click
3
+
4
+ from vantage6.client import UserClient
5
+ from vantage6.cli.utils import error
6
+ from vantage6.cli.test.common.diagnostic_runner import DiagnosticRunner
7
+
8
+
9
+ @click.command()
10
+ @click.option("--host", type=str, default="http://localhost",
11
+ help="URL of the server")
12
+ @click.option("--port", type=int, default=5000, help="Port of the server")
13
+ @click.option("--api-path", type=str, default="/api",
14
+ help="API path of the server")
15
+ @click.option("--username", type=str, default="root",
16
+ help="Username of vantage6 user account to create the task with")
17
+ @click.option("--password", type=str, default="root",
18
+ help="Password of vantage6 user account to create the task with")
19
+ @click.option("--collaboration", type=int, default=1,
20
+ help="ID of the collaboration to create the task in")
21
+ @click.option("-o", "--organizations", type=int, default=[], multiple=True,
22
+ help="ID(s) of the organization(s) to create the task for")
23
+ @click.option("--all-nodes", is_flag=True,
24
+ help="Run the diagnostic test on all nodes in the collaboration")
25
+ @click.option("--online-only", is_flag=True,
26
+ help="Run the diagnostic test on only nodes that are online")
27
+ @click.option("--no-vpn", is_flag=True,
28
+ help="Don't execute VPN tests")
29
+ def cli_test_features(
30
+ host: str, port: int, api_path: str, username: str, password: str,
31
+ collaboration: int, organizations: list[int] | None, all_nodes: bool,
32
+ online_only: bool, no_vpn: bool
33
+ ) -> list[dict]:
34
+ """
35
+ Run diagnostic checks on an existing vantage6 network.
36
+
37
+ This command will create a task in the requested collaboration that will
38
+ test the functionality of vantage6, and will report back the results.
39
+ """
40
+ if all_nodes and organizations:
41
+ error("Cannot use --all-nodes and --organization at the same time.")
42
+ sys.exit(1)
43
+
44
+ if all_nodes or not organizations:
45
+ organizations = None
46
+
47
+ client = UserClient(host=host, port=port, path=api_path,
48
+ log_level='critical')
49
+ client.authenticate(username=username, password=password)
50
+ client.setup_encryption(None)
51
+ diagnose = DiagnosticRunner(client, collaboration, organizations,
52
+ online_only)
53
+ res = diagnose(base=True, vpn=not no_vpn)
54
+ return res
@@ -0,0 +1,82 @@
1
+ from pathlib import Path
2
+ import click
3
+
4
+ from vantage6.cli.utils import info
5
+ from vantage6.cli.dev.create import create_demo_network
6
+ from vantage6.cli.dev.start import start_demo_network
7
+ from vantage6.cli.dev.stop import stop_demo_network
8
+ from vantage6.cli.dev.remove import remove_demo_network
9
+ from vantage6.cli.utils import prompt_config_name, check_config_name_allowed
10
+ from vantage6.cli.test.feature_tester import cli_test_features
11
+
12
+
13
+ @click.command()
14
+ @click.option('-n', '--name', default=None, type=str,
15
+ help="Name for your development setup")
16
+ @click.option('--server-url', type=str, default='http://host.docker.internal',
17
+ help='Server URL to point to. If you are using Docker Desktop, '
18
+ 'the default http://host.docker.internal should not be changed.')
19
+ @click.option('-i', '--image', type=str, default=None,
20
+ help='Server Docker image to use')
21
+ @click.option('--keep', type=bool, default=False,
22
+ help='Keep the dev network after finishing the test')
23
+ @click.option('--extra-server-config', type=click.Path(exists=True),
24
+ default=None, help='YAML File with additional server '
25
+ 'configuration. This will be appended to the server '
26
+ 'configuration file')
27
+ @click.option('--extra-node-config', type=click.Path('rb'), default=None,
28
+ help='YAML File with additional node configuration. This will be'
29
+ ' appended to each of the node configuration files')
30
+ @click.pass_context
31
+ def cli_test_integration(
32
+ click_ctx: click.Context, name: str, server_url: str, image: str,
33
+ keep: bool = False, extra_server_config: Path = None,
34
+ extra_node_config: Path = None
35
+ ) -> list[dict]:
36
+ """
37
+ Create dev network and run diagnostic checks on it.
38
+
39
+ This is a full integration test of the vantage6 network. It will create
40
+ a test server with some nodes using the `vdev` commands, and then run the
41
+ v6-diagnostics algorithm to test all functionality.
42
+ """
43
+ # get name for the development setup - if not given - and check if it is
44
+ # allowed
45
+ name = prompt_config_name(name)
46
+ check_config_name_allowed(name)
47
+
48
+ # create server & node configurations and create test resources (
49
+ # collaborations, organizations, etc)
50
+ click_ctx.invoke(
51
+ create_demo_network, name=name, num_nodes=3, server_url=server_url,
52
+ server_port=5000, image=image, extra_server_config=extra_server_config,
53
+ extra_node_config=extra_node_config
54
+ )
55
+
56
+ # start the server and nodes
57
+ click_ctx.invoke(
58
+ start_demo_network, name=name, system_folders=True, server_image=image,
59
+ node_image=image
60
+ )
61
+
62
+ # run the diagnostic tests
63
+ # TODO the username and password should be coordinated with the vdev
64
+ # command - at present it spits out this username/password combination by
65
+ # default but both should be defined in the same place
66
+ # TODO VPN testing is always excluded - allow to include it with a flag
67
+ # when vdev commands can handle extra config parameters
68
+ diagnose_results = click_ctx.invoke(
69
+ cli_test_features, host="http://localhost", port=5000, api_path='/api',
70
+ username='org_1-admin', password='password', collaboration=1,
71
+ organizations=None, all_nodes=True, online_only=False, no_vpn=True
72
+ )
73
+
74
+ # clean up the test resources
75
+ click_ctx.invoke(stop_demo_network, name=name, system_folders=True)
76
+ if not keep:
77
+ click_ctx.invoke(remove_demo_network, name=name, system_folders=True)
78
+ else:
79
+ info("Keeping the demo network {name}. You can start it with `v6 dev "
80
+ "start-demo-network`")
81
+
82
+ return diagnose_results
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vantage6
3
- Version: 4.1.3
3
+ Version: 4.2.0rc2
4
4
  Summary: vantage6 command line interface
5
5
  Home-page: https://github.com/vantage6/vantage6
6
6
  Requires-Python: >=3.10
@@ -10,12 +10,13 @@ Requires-Dist: colorama ==0.4.6
10
10
  Requires-Dist: copier ==8.3.0
11
11
  Requires-Dist: docker ==6.1.2
12
12
  Requires-Dist: ipython ==8.10.0
13
- Requires-Dist: jinja2 ==3.1.2
13
+ Requires-Dist: jinja2 ==3.1.3
14
14
  Requires-Dist: questionary ==1.10.0
15
+ Requires-Dist: rich ==13.5.2
15
16
  Requires-Dist: schema ==0.7.5
16
17
  Requires-Dist: SQLAlchemy ==1.4.46
17
- Requires-Dist: vantage6-common ==4.1.3
18
- Requires-Dist: vantage6-client ==4.1.3
18
+ Requires-Dist: vantage6-common ==4.2.0rc2
19
+ Requires-Dist: vantage6-client ==4.2.0rc2
19
20
  Provides-Extra: dev
20
21
  Requires-Dist: coverage ==6.4.4 ; extra == 'dev'
21
22
 
@@ -1,21 +1,21 @@
1
1
  tests_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  tests_cli/test_example.py,sha256=TV8JA52UR-Yh1WeVwx8g8ngA9ZTiRCwsJkUKxsXUXMg,147
3
- tests_cli/test_node_cli.py,sha256=LtnGjHbCJq95eH3vVIeSbVvbX1_DMgP0mVLRqcdP4ss,15454
4
- tests_cli/test_server_cli.py,sha256=AYI6NQpI76OxIOHvWx87B1E-q3hgc6Dh4efD82LYuOA,6202
3
+ tests_cli/test_node_cli.py,sha256=8tfCNvdZT7uMgOJ0QbBlY73kox7RUIDHhxQWVtJiSiA,15454
4
+ tests_cli/test_server_cli.py,sha256=hWnGEV6JnXN_SqpUiQxCzITBlbYKRykb8B4MkFX7aEU,5798
5
5
  tests_cli/test_wizard.py,sha256=dRrOScN79z8nKGNWmn6tPAVfnDOmCsGckiDJMKufhms,3580
6
- vantage6/cli/__build__,sha256=X-zrZv_IbzjZUnhsbWlsecLbwjndTpG0ZynXOif7V-k,1
6
+ vantage6/cli/__build__,sha256=1HNeOiZeFu7gP1lxi5tdAwGcB9i2xR-Q2jpmbuwTqzU,1
7
7
  vantage6/cli/__init__.py,sha256=dEbhLHe9VfU3otY3CQkMVrj39uPGWjqzAvHW2B2Flgs,62
8
- vantage6/cli/_version.py,sha256=cdx52n-T9P3JZIQOBAMBO36zH56L9GBPhTniacMXo2M,684
9
- vantage6/cli/cli.py,sha256=QFyW61HP3FkcpGD8OkAfqN-oUYWdtDs_uIou2Sy7xZE,4280
8
+ vantage6/cli/_version.py,sha256=vNBdXaoKkSVejAo0RNxmIyEGekryfMYJeh21XE5SACo,688
9
+ vantage6/cli/cli.py,sha256=Tz7dj6X8GVPrzHd9GehURdbPFIDzGvyYW2MXRUJwFs4,4875
10
10
  vantage6/cli/configuration_manager.py,sha256=4EHlOW16nfIiplzA0sw9udc4-VLKcmdKgWzzFa5Nw7A,3841
11
11
  vantage6/cli/configuration_wizard.py,sha256=c8L4xm9_HYjAkyTmRmLDMwzTDqdKdgnBZy915TAIfRI,11569
12
12
  vantage6/cli/context.py,sha256=dJZWmOZGqQ3MazrPu8HbnXbNC2tzHcLjns9KoIHGpFk,13045
13
- vantage6/cli/globals.py,sha256=fWPiKGub9Chj7MM8xfm87arMisqkdoZdZqgvWxbSmLA,909
13
+ vantage6/cli/globals.py,sha256=yDYhbzWgwrd549lTkVH7g59orQuN30zwP60VHiUiMD4,1027
14
14
  vantage6/cli/utils.py,sha256=Lx1xYKXDBeHjJpXGkVAhidy79P-zV3TOQYfa2nMbvSs,2353
15
15
  vantage6/cli/algorithm/create.py,sha256=cUjehkwRa6eWRSnWLPk9iLk4_qf8x_MI2WxmaAUMVT0,1496
16
16
  vantage6/cli/algorithm/update.py,sha256=0GCkFIRGV-HWHWagTSqkE9_SPuVrTMQQhtytpitMiQc,821
17
- vantage6/cli/dev/create.py,sha256=orkcbMcdQg5OQsmi3EuBGzMsuWsG73dx0SqbWW_TeDc,11256
18
- vantage6/cli/dev/remove.py,sha256=qNqZwAaTIqdjkHIcr1pjTMSqlTDmcaPz87NkSMhT-ws,2362
17
+ vantage6/cli/dev/create.py,sha256=Xij_DOunyjzoDulpIrL00kasHiXbjp_YJWR3OjScCZc,13249
18
+ vantage6/cli/dev/remove.py,sha256=m5Yjt7U3pTmlZm1f8G8JsxMm9k-CWTgoy-sCZ7GTnZA,2211
19
19
  vantage6/cli/dev/start.py,sha256=IJbXzezMx4MK7rO9nx3AvidEZfdUL5mX0gA7AlwETLU,1629
20
20
  vantage6/cli/dev/stop.py,sha256=XDvjyxxU4gVnC9sKHxYMLYPmDwRq_4lnpXE9imO614Q,993
21
21
  vantage6/cli/node/attach.py,sha256=n7mJaDJq7tngMVigXe0_JBAdueV_5YEz8FmVOvAF-PU,2152
@@ -26,7 +26,7 @@ vantage6/cli/node/list.py,sha256=b8ht09bvzVFiQx909r8zeTruCWC2uLyrNVeiipCjgAI,170
26
26
  vantage6/cli/node/new.py,sha256=01sp9005TidvHA_awuaahBHV1-LImspStLTYhruek_A,1858
27
27
  vantage6/cli/node/remove.py,sha256=kLMK2Xsfgwrb3FuqRc9cKCadc8p0P3dLTLt9wN1_sBg,3564
28
28
  vantage6/cli/node/set_api_key.py,sha256=cJXWe1G5m2LTlyhBX85KFlCcL7voO-YNHUvhnsrwHhE,1796
29
- vantage6/cli/node/start.py,sha256=Q6VvsnLVlwDhKmQbJ3xrMlxLTqSde3wdrZHA3BMclAA,11886
29
+ vantage6/cli/node/start.py,sha256=ojv2csH0PcqfoT9rZNJpmuGerqBN74Ypt5bta6caLic,12404
30
30
  vantage6/cli/node/stop.py,sha256=08SWafKBpCY_pnMopRkcIcHNaBoxqRHmlDSfQDzWjuQ,2704
31
31
  vantage6/cli/node/version.py,sha256=HD0iCf2VGZK8HVYfNcMpDAQdxYQ--5ff10XYCsX7Hf4,1843
32
32
  vantage6/cli/node/common/__init__.py,sha256=nUDpIehzfX9Zd3tL2Wa9nP5940SH27gcXGRiCHcxjrA,3166
@@ -39,16 +39,20 @@ vantage6/cli/server/files.py,sha256=owBO6lj_ucHY00MxJUOOdacz4NbdOqp-vrfkVrGjPUE,
39
39
  vantage6/cli/server/import_.py,sha256=Up3IAK3c9jyFpr7y9_zl4HNViFj6jT9OLh_S6v6t5Hs,4611
40
40
  vantage6/cli/server/list.py,sha256=p5SRRGsz6G83Hc53eYoySWi32egm7jie_lo1gkS0NCk,1734
41
41
  vantage6/cli/server/new.py,sha256=ZHnjWlEKXOMdu6WYHDcrD62Qc2au-YuYIv2SW2KXS7M,1913
42
+ vantage6/cli/server/remove.py,sha256=1Ogw2npPA9QsExVforzLe3dI3eTwxuBd9oasviX4Z_g,1182
42
43
  vantage6/cli/server/shell.py,sha256=SRw-CrD_ro9DRfqCI6IlifJk_UDr6EIke5ypN4-85yo,1384
43
44
  vantage6/cli/server/start.py,sha256=-qD1rB5dRXWLL13OVWFb4DwdshGF708bL19WOlO9uUo,10952
44
- vantage6/cli/server/stop.py,sha256=Lij7NSiVcgPWyQFB4-p-P2zbkdWOZAk08LKHZhYqxU0,5446
45
+ vantage6/cli/server/stop.py,sha256=aEOqa2hYPLTGPkMBzFrFI4FLP0zMskjYD1vqzDyKEog,4005
45
46
  vantage6/cli/server/version.py,sha256=upvy6VdO3XGfovEW-2Ybc0c0niM2ieCOrz1SlsBrqkw,1743
46
47
  vantage6/cli/server/common/__init__.py,sha256=kUcDOfGuNvf8j7g5er9D_7z8IHRk53QBQ2E0NcZ_1iE,4818
47
- vantage6/cli/template/node_config.j2,sha256=-IyUquZl4jRxb5DRBWvEbZFwz2lFtHG_1reZoSkeees,495
48
- vantage6/cli/template/server_config.j2,sha256=d6O_K-VzJC3GHbCGVbXRWB4jTEzJHPz1VC7t3U8e0Tc,367
48
+ vantage6/cli/template/node_config.j2,sha256=ebTifJuRlfUhjtI8jm9A2SWXGpUcg8L-XUBInSwAppA,522
49
+ vantage6/cli/template/server_config.j2,sha256=rKXMky8rA7Lnf5USlPx0Es9AskwmL4bILWfACPvygv4,394
49
50
  vantage6/cli/template/server_import_config.j2,sha256=PRB0ym_FYjx9vhkmY9C0xzlv_85Y5kBfWdUYs089bNQ,1844
50
- vantage6-4.1.3.dist-info/METADATA,sha256=zTHnjtPLjJKpSSil59S4uEQ3ixY8eqZz-8D8hm4cpZg,9729
51
- vantage6-4.1.3.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
52
- vantage6-4.1.3.dist-info/entry_points.txt,sha256=YFBvwjxoeAGxYyPC-YevEgOBBYRGaXkS6jiOGGCLNy0,157
53
- vantage6-4.1.3.dist-info/top_level.txt,sha256=CYDIBS8jEfFq5YCs_Fuit54K9-3wdosZppTrsymIoUk,19
54
- vantage6-4.1.3.dist-info/RECORD,,
51
+ vantage6/cli/test/feature_tester.py,sha256=8MJGotRonEje6GVTkqOr5u1LbZi3W4A4gQnf5OYt-2I,2368
52
+ vantage6/cli/test/integration_test.py,sha256=GpYG0uHoD1lVKwtSFk_QhdR1e_AYzKWIVPDI5CSwJ8o,3669
53
+ vantage6/cli/test/common/diagnostic_runner.py,sha256=j6MWV0_dKZScYBwd--3_dv_JSeWyeUmp8T7E-7ZSjs8,6755
54
+ vantage6-4.2.0rc2.dist-info/METADATA,sha256=L8_VGCM7QnL-1uiADLAJ_kFq47UEvGI5cK2dL7RFSSA,9767
55
+ vantage6-4.2.0rc2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
56
+ vantage6-4.2.0rc2.dist-info/entry_points.txt,sha256=YFBvwjxoeAGxYyPC-YevEgOBBYRGaXkS6jiOGGCLNy0,157
57
+ vantage6-4.2.0rc2.dist-info/top_level.txt,sha256=CYDIBS8jEfFq5YCs_Fuit54K9-3wdosZppTrsymIoUk,19
58
+ vantage6-4.2.0rc2.dist-info/RECORD,,