vantage6 4.9.1__tar.gz → 4.10.0rc2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of vantage6 might be problematic. Click here for more details.

Files changed (82) hide show
  1. {vantage6-4.9.1 → vantage6-4.10.0rc2}/PKG-INFO +1 -1
  2. {vantage6-4.9.1 → vantage6-4.10.0rc2}/tests_cli/test_node_cli.py +6 -0
  3. {vantage6-4.9.1 → vantage6-4.10.0rc2}/tests_cli/test_wizard.py +5 -5
  4. vantage6-4.10.0rc2/vantage6/cli/__build__ +1 -0
  5. vantage6-4.10.0rc2/vantage6/cli/__init__.py +3 -0
  6. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/_version.py +1 -1
  7. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/algorithm/create.py +26 -8
  8. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/algorithm/update.py +5 -1
  9. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/algostore/attach.py +8 -4
  10. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/algostore/new.py +7 -1
  11. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/common/utils.py +8 -4
  12. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/configuration_manager.py +1 -0
  13. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/configuration_wizard.py +60 -50
  14. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/dev/create.py +8 -6
  15. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/dev/remove.py +18 -1
  16. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/attach.py +7 -3
  17. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/clean.py +6 -2
  18. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/common/__init__.py +20 -3
  19. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/new.py +5 -1
  20. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/restart.py +7 -3
  21. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/set_api_key.py +5 -1
  22. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/start.py +11 -3
  23. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/stop.py +7 -3
  24. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/version.py +7 -3
  25. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/server/attach.py +7 -3
  26. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/server/new.py +5 -1
  27. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/server/start.py +1 -1
  28. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/server/stop.py +7 -3
  29. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/test/feature_tester.py +10 -3
  30. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/utils.py +5 -1
  31. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6.egg-info/PKG-INFO +1 -1
  32. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6.egg-info/requires.txt +2 -2
  33. vantage6-4.9.1/vantage6/cli/__build__ +0 -1
  34. vantage6-4.9.1/vantage6/cli/__init__.py +0 -3
  35. {vantage6-4.9.1 → vantage6-4.10.0rc2}/setup.cfg +0 -0
  36. {vantage6-4.9.1 → vantage6-4.10.0rc2}/setup.py +0 -0
  37. {vantage6-4.9.1 → vantage6-4.10.0rc2}/tests_cli/__init__.py +0 -0
  38. {vantage6-4.9.1 → vantage6-4.10.0rc2}/tests_cli/test_example.py +0 -0
  39. {vantage6-4.9.1 → vantage6-4.10.0rc2}/tests_cli/test_server_cli.py +0 -0
  40. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/algostore/files.py +0 -0
  41. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/algostore/list.py +0 -0
  42. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/algostore/remove.py +0 -0
  43. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/algostore/start.py +0 -0
  44. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/algostore/stop.py +0 -0
  45. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/cli.py +0 -0
  46. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/common/decorator.py +0 -0
  47. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/common/start.py +0 -0
  48. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/context/__init__.py +0 -0
  49. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/context/algorithm_store.py +0 -0
  50. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/context/base_server.py +0 -0
  51. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/context/node.py +0 -0
  52. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/context/server.py +0 -0
  53. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/dev/data/olympic_athletes_2016.csv +0 -0
  54. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/dev/start.py +0 -0
  55. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/dev/stop.py +0 -0
  56. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/dev/utils.py +0 -0
  57. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/globals.py +0 -0
  58. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/create_private_key.py +0 -0
  59. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/files.py +0 -0
  60. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/list.py +0 -0
  61. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/node/remove.py +0 -0
  62. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/rabbitmq/__init__.py +0 -0
  63. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/rabbitmq/definitions.py +0 -0
  64. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/rabbitmq/queue_manager.py +0 -0
  65. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/rabbitmq/rabbitmq.config +0 -0
  66. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/server/common/__init__.py +0 -0
  67. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/server/files.py +0 -0
  68. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/server/import_.py +0 -0
  69. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/server/list.py +0 -0
  70. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/server/remove.py +0 -0
  71. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/server/shell.py +0 -0
  72. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/server/version.py +0 -0
  73. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/template/algo_store_config.j2 +0 -0
  74. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/template/node_config.j2 +0 -0
  75. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/template/server_config.j2 +0 -0
  76. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/template/server_import_config.j2 +0 -0
  77. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/test/common/diagnostic_runner.py +0 -0
  78. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6/cli/test/integration_test.py +0 -0
  79. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6.egg-info/SOURCES.txt +0 -0
  80. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6.egg-info/dependency_links.txt +0 -0
  81. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6.egg-info/entry_points.txt +0 -0
  82. {vantage6-4.9.1 → vantage6-4.10.0rc2}/vantage6.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vantage6
3
- Version: 4.9.1
3
+ Version: 4.10.0rc2
4
4
  Summary: vantage6 command line interface
5
5
  Home-page: https://github.com/vantage6/vantage6
6
6
  Requires-Python: >=3.10
@@ -244,9 +244,15 @@ class NodeCLITest(unittest.TestCase):
244
244
 
245
245
  runner = CliRunner()
246
246
 
247
+ # Should fail when starting node with non-existing database CSV file
247
248
  with runner.isolated_filesystem():
248
249
  result = runner.invoke(cli_node_start, ["--name", "some-name"])
250
+ self.assertEqual(result.exit_code, 1)
249
251
 
252
+ # now do it with a SQL database which doesn't have to be an existing file
253
+ ctx.databases = [{"label": "some_label", "uri": "data.db", "type": "sql"}]
254
+ with runner.isolated_filesystem():
255
+ result = runner.invoke(cli_node_start, ["--name", "some-name"])
250
256
  self.assertEqual(result.exit_code, 0)
251
257
 
252
258
  def _setup_stop_test(self, containers):
@@ -33,8 +33,8 @@ class WizardTest(unittest.TestCase):
33
33
  """An error is printed when docker is not running"""
34
34
 
35
35
  with patch(f"{module_path}.q") as q:
36
- q.prompt.side_effect = self.prompts
37
- q.confirm.return_value.ask.side_effect = [
36
+ q.unsafe_prompt.side_effect = self.prompts
37
+ q.confirm.return_value.unsafe_ask.side_effect = [
38
38
  True, # add a database
39
39
  False, # don't enable two-factor authentication
40
40
  True, # add VPN server
@@ -79,8 +79,8 @@ class WizardTest(unittest.TestCase):
79
79
 
80
80
  def test_server_wizard(self):
81
81
  with patch(f"{module_path}.q") as q:
82
- q.prompt.side_effect = self.prompts
83
- q.confirm.return_value.ask.side_effect = [
82
+ q.unsafe_prompt.side_effect = self.prompts
83
+ q.confirm.return_value.unsafe_ask.side_effect = [
84
84
  True,
85
85
  True,
86
86
  True,
@@ -134,7 +134,7 @@ class WizardTest(unittest.TestCase):
134
134
  available_configurations.return_value = [[config], []]
135
135
 
136
136
  with patch(f"{module_path}.q") as q:
137
- q.select.return_value.ask.return_value = "vtg6"
137
+ q.select.return_value.unsafe_ask.return_value = "vtg6"
138
138
  name = select_configuration_questionaire(InstanceType.NODE, True)
139
139
 
140
140
  self.assertEqual(name, "vtg6")
@@ -0,0 +1 @@
1
+ 2
@@ -0,0 +1,3 @@
1
+ """Command line interface for the vantage6 infrastructure."""
2
+
3
+ from ._version import version_info, __version__ # noqa: F401
@@ -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, 9, 1, "final", __build__, 0)
10
+ version_info = (4, 10, 0, "candidate", __build__, 0)
11
11
 
12
12
  # Module version stage suffix map
13
13
  _specifier_ = {"alpha": "a", "beta": "b", "candidate": "rc", "final": ""}
@@ -33,14 +33,11 @@ def cli_algorithm_create(name: str, directory: str) -> dict:
33
33
  can be used to build an appropriate Docker image that can be used as a
34
34
  vantage6 algorithm.
35
35
  """
36
- if not name:
37
- name = q.text("Name of your new algorithm:").ask()
38
-
39
- if not directory:
40
- default_dir = str(Path(os.getcwd()) / name)
41
- directory = q.text(
42
- "Directory to put the algorithm in:", default=default_dir
43
- ).ask()
36
+ try:
37
+ name, directory = _get_user_input(name, directory)
38
+ except KeyboardInterrupt:
39
+ info("Aborted by user!")
40
+ return
44
41
 
45
42
  # Create the template. The `unsafe` flag is used to allow running a Python script
46
43
  # after creating the template that cleans up some things.
@@ -49,3 +46,24 @@ def cli_algorithm_create(name: str, directory: str) -> dict:
49
46
  )
50
47
  info("Template created!")
51
48
  info(f"You can find your new algorithm in: {directory}")
49
+
50
+
51
+ def _get_user_input(name: str, directory: str) -> None:
52
+ """Get user input for the algorithm creation
53
+
54
+ Parameters
55
+ ----------
56
+ name : str
57
+ Name for the new algorithm
58
+ directory : str
59
+ Directory to put the algorithm into
60
+ """
61
+ if not name:
62
+ name = q.text("Name of your new algorithm:").unsafe_ask()
63
+
64
+ if not directory:
65
+ default_dir = str(Path(os.getcwd()) / name)
66
+ directory = q.text(
67
+ "Directory to put the algorithm in:", default=default_dir
68
+ ).unsafe_ask()
69
+ return name, directory
@@ -34,7 +34,11 @@ def cli_algorithm_update(directory: str, change_answers: bool) -> dict:
34
34
  """
35
35
  if not directory:
36
36
  default_dir = str(Path(os.getcwd()))
37
- directory = q.text("Algorithm directory:", default=default_dir).ask()
37
+ try:
38
+ directory = q.text("Algorithm directory:", default=default_dir).unsafe_ask()
39
+ except KeyboardInterrupt:
40
+ info("Aborted by user!")
41
+ return
38
42
 
39
43
  info("Updating template...")
40
44
  try:
@@ -30,10 +30,14 @@ def cli_algo_store_attach(name: str, system_folders: bool) -> None:
30
30
  running_server_names = [container.name for container in running_servers]
31
31
 
32
32
  if not name:
33
- name = q.select(
34
- "Select the algorithm store you wish to attach:",
35
- choices=running_server_names,
36
- ).ask()
33
+ try:
34
+ name = q.select(
35
+ "Select the algorithm store you wish to attach:",
36
+ choices=running_server_names,
37
+ ).unsafe_ask()
38
+ except KeyboardInterrupt:
39
+ error("Aborted by user!")
40
+ return
37
41
  else:
38
42
  post_fix = "system" if system_folders else "user"
39
43
  name = f"{APPNAME}-{name}-{post_fix}-{InstanceType.ALGORITHM_STORE}"
@@ -45,7 +45,13 @@ def cli_algo_store_new(name: str, system_folders: bool) -> None:
45
45
  exit(1)
46
46
 
47
47
  # create config in ctx location
48
- cfg_file = configuration_wizard(InstanceType.ALGORITHM_STORE, name, system_folders)
48
+ try:
49
+ cfg_file = configuration_wizard(
50
+ InstanceType.ALGORITHM_STORE, name, system_folders
51
+ )
52
+ except KeyboardInterrupt:
53
+ error("Configuration creation aborted.")
54
+ exit(1)
49
55
  info(f"New configuration created: {Fore.GREEN}{cfg_file}{Style.RESET_ALL}")
50
56
 
51
57
  flag = "" if system_folders else "--user"
@@ -38,10 +38,14 @@ def get_server_name(
38
38
  f"{instance_type}s that are running"
39
39
  )
40
40
  exit(1)
41
- name = q.select(
42
- f"Select the {instance_type} you wish to inspect:",
43
- choices=running_server_names,
44
- ).ask()
41
+ try:
42
+ name = q.select(
43
+ f"Select the {instance_type} you wish to inspect:",
44
+ choices=running_server_names,
45
+ ).unsafe_ask()
46
+ except KeyboardInterrupt:
47
+ error("Aborted by user!")
48
+ exit(1)
45
49
  else:
46
50
  post_fix = "system" if system_folders else "user"
47
51
  name = f"{APPNAME}-{name}-{post_fix}"
@@ -29,6 +29,7 @@ class ServerConfiguration(Configuration):
29
29
  "allow_drop_all": Use(bool),
30
30
  "logging": {**LOGGING_VALIDATORS, "file": Use(str)},
31
31
  Optional("server_name"): str,
32
+ Optional("runs_data_cleanup_days"): Use(int),
32
33
  }
33
34
 
34
35
 
@@ -1,7 +1,7 @@
1
- import questionary as q
2
-
3
1
  from pathlib import Path
4
2
 
3
+ import questionary as q
4
+
5
5
  from vantage6.common import generate_apikey
6
6
  from vantage6.common.globals import (
7
7
  DATABASE_TYPES,
@@ -36,7 +36,7 @@ def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
36
36
  dict
37
37
  Dictionary with the new node configuration
38
38
  """
39
- config = q.prompt(
39
+ config = q.unsafe_prompt(
40
40
  [
41
41
  {"type": "text", "name": "api_key", "message": "Enter given api-key:"},
42
42
  {
@@ -57,7 +57,7 @@ def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
57
57
  else str(Ports.DEV_SERVER.value)
58
58
  )
59
59
 
60
- config = config | q.prompt(
60
+ config = config | q.unsafe_prompt(
61
61
  [
62
62
  {
63
63
  "type": "text",
@@ -81,8 +81,8 @@ def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
81
81
  )
82
82
 
83
83
  config["databases"] = list()
84
- while q.confirm("Do you want to add a database?").ask():
85
- db_label = q.prompt(
84
+ while q.confirm("Do you want to add a database?").unsafe_ask():
85
+ db_label = q.unsafe_prompt(
86
86
  [
87
87
  {
88
88
  "type": "text",
@@ -92,10 +92,10 @@ def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
92
92
  }
93
93
  ]
94
94
  )
95
- db_path = q.prompt(
95
+ db_path = q.unsafe_prompt(
96
96
  [{"type": "text", "name": "uri", "message": "Database URI:"}]
97
97
  )
98
- db_type = q.select("Database type:", choices=DATABASE_TYPES).ask()
98
+ db_type = q.select("Database type:", choices=DATABASE_TYPES).unsafe_ask()
99
99
 
100
100
  config["databases"].append(
101
101
  {"label": db_label.get("label"), "uri": db_path.get("uri"), "type": db_type}
@@ -103,18 +103,18 @@ def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
103
103
 
104
104
  is_add_vpn = q.confirm(
105
105
  "Do you want to connect to a VPN server?", default=False
106
- ).ask()
106
+ ).unsafe_ask()
107
107
  if is_add_vpn:
108
108
  config["vpn_subnet"] = q.text(
109
109
  message="Subnet of the VPN server you want to connect to:",
110
110
  default="10.76.0.0/16",
111
- ).ask()
111
+ ).unsafe_ask()
112
112
 
113
113
  is_policies = q.confirm(
114
114
  "Do you want to limit the algorithms allowed to run on your node? This "
115
115
  "should always be done for production scenarios.",
116
116
  default=True,
117
- ).ask()
117
+ ).unsafe_ask()
118
118
  policies = {}
119
119
  if is_policies:
120
120
  info(
@@ -124,12 +124,12 @@ def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
124
124
  )
125
125
  ask_single_algorithms = q.confirm(
126
126
  "Do you want to enter a list of allowed algorithms?"
127
- ).ask()
127
+ ).unsafe_ask()
128
128
  if ask_single_algorithms:
129
129
  policies[NodePolicy.ALLOWED_ALGORITHMS.value] = _get_allowed_algorithms()
130
130
  ask_algorithm_stores = q.confirm(
131
131
  "Do you want to allow algorithms from specific algorithm stores?"
132
- ).ask()
132
+ ).unsafe_ask()
133
133
  if ask_algorithm_stores:
134
134
  policies[NodePolicy.ALLOWED_ALGORITHM_STORES.value] = (
135
135
  _get_allowed_algorithm_stores()
@@ -141,7 +141,7 @@ def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
141
141
  "stores? If not, algorithms will be allowed if they are in either the "
142
142
  "list of allowed algorithms or one of the allowed algorithm stores.",
143
143
  default=True,
144
- ).ask()
144
+ ).unsafe_ask()
145
145
  policies["allow_either_whitelist_or_store"] = not require_both_whitelists
146
146
  if policies:
147
147
  config["policies"] = policies
@@ -149,7 +149,7 @@ def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
149
149
  res = q.select(
150
150
  "Which level of logging would you like?",
151
151
  choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "NOTSET"],
152
- ).ask()
152
+ ).unsafe_ask()
153
153
 
154
154
  config["logging"] = {
155
155
  "level": res,
@@ -178,7 +178,7 @@ def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
178
178
  "If you continue, you should provide your collaboration "
179
179
  "settings manually."
180
180
  )
181
- if q.confirm("Do you want to abort?", default=True).ask():
181
+ if q.confirm("Do you want to abort?", default=True).unsafe_ask():
182
182
  exit(0)
183
183
 
184
184
  if client.whoami is not None:
@@ -190,11 +190,13 @@ def node_configuration_questionaire(dirs: dict, instance_name: str) -> dict:
190
190
  f"Encryption is {'enabled' if encryption else 'disabled'}"
191
191
  f" for this collaboration. Accept?",
192
192
  default=True,
193
- ).ask()
193
+ ).unsafe_ask()
194
194
  else:
195
- encryption = q.confirm("Enable encryption?", default=True).ask()
195
+ encryption = q.confirm("Enable encryption?", default=True).unsafe_ask()
196
196
 
197
- private_key = "" if not encryption else q.text("Path to private key file:").ask()
197
+ private_key = (
198
+ "" if not encryption else q.text("Path to private key file:").unsafe_ask()
199
+ )
198
200
 
199
201
  config["encryption"] = {
200
202
  "enabled": encryption is True or encryption == "true",
@@ -219,24 +221,22 @@ def _get_allowed_algorithms() -> list[str]:
219
221
  "use strings to provide one algorithm at a time."
220
222
  )
221
223
  info("Examples:")
222
- # pylint: disable=W1401
223
- # flake8: noqa: W605
224
- info("^harbor2\.vantage6\.ai/demo/average$ Allow the demo average algorithm")
224
+ info(r"^harbor2\.vantage6\.ai/demo/average$ Allow the demo average algorithm")
225
225
  info(
226
- "^harbor2\.vantage6\.ai/algorithms/.* Allow all algorithms from "
226
+ r"^harbor2\.vantage6\.ai/algorithms/.* Allow all algorithms from "
227
227
  "harbor2.vantage6.ai/algorithms"
228
228
  )
229
229
  info(
230
- "^harbor2\.vantage6\.ai/demo/average@sha256:82becede...$ Allow a "
230
+ r"^harbor2\.vantage6\.ai/demo/average@sha256:82becede...$ Allow a "
231
231
  "specific hash of average algorithm"
232
232
  )
233
233
  allowed_algorithms = []
234
234
  while True:
235
- algo = q.text(message="Enter your algorithm expression:").ask()
235
+ algo = q.text(message="Enter your algorithm expression:").unsafe_ask()
236
236
  allowed_algorithms.append(algo)
237
237
  if not q.confirm(
238
238
  "Do you want to add another algorithm expression?", default=True
239
- ).ask():
239
+ ).unsafe_ask():
240
240
  break
241
241
  return allowed_algorithms
242
242
 
@@ -261,16 +261,16 @@ def _get_allowed_algorithm_stores() -> list[str]:
261
261
  "community store"
262
262
  )
263
263
  info(
264
- "^https://*\.vantage6\.ai$ Allow all algorithms from any "
264
+ r"^https://*\.vantage6\.ai$ Allow all algorithms from any "
265
265
  "store hosted on vantage6.ai"
266
266
  )
267
267
  allowed_algorithm_stores = []
268
268
  while True:
269
- store = q.text(message="Enter the URL of the algorithm store:").ask()
269
+ store = q.text(message="Enter the URL of the algorithm store:").unsafe_ask()
270
270
  allowed_algorithm_stores.append(store)
271
271
  if not q.confirm(
272
272
  "Do you want to add another algorithm store?", default=True
273
- ).ask():
273
+ ).unsafe_ask():
274
274
  break
275
275
  return allowed_algorithm_stores
276
276
 
@@ -292,7 +292,7 @@ def _get_common_server_config(instance_type: InstanceType, instance_name: str) -
292
292
  dict
293
293
  Dictionary with new (partial) server configuration
294
294
  """
295
- config = q.prompt(
295
+ config = q.unsafe_prompt(
296
296
  [
297
297
  {
298
298
  "type": "text",
@@ -315,7 +315,7 @@ def _get_common_server_config(instance_type: InstanceType, instance_name: str) -
315
315
  )
316
316
 
317
317
  config.update(
318
- q.prompt(
318
+ q.unsafe_prompt(
319
319
  [
320
320
  {
321
321
  "type": "text",
@@ -336,7 +336,7 @@ def _get_common_server_config(instance_type: InstanceType, instance_name: str) -
336
336
  res = q.select(
337
337
  "Which level of logging would you like?",
338
338
  choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL", "NOTSET"],
339
- ).ask()
339
+ ).unsafe_ask()
340
340
 
341
341
  config["logging"] = {
342
342
  "level": res,
@@ -378,11 +378,11 @@ def server_configuration_questionaire(instance_name: str) -> dict:
378
378
 
379
379
  config = _get_common_server_config(InstanceType.SERVER, instance_name)
380
380
 
381
- constant_jwt_secret = q.confirm("Do you want a constant JWT secret?").ask()
381
+ constant_jwt_secret = q.confirm("Do you want a constant JWT secret?").unsafe_ask()
382
382
  if constant_jwt_secret:
383
383
  config["jwt_secret_key"] = generate_apikey()
384
384
 
385
- is_mfa = q.confirm("Do you want to enforce two-factor authentication?").ask()
385
+ is_mfa = q.confirm("Do you want to enforce two-factor authentication?").unsafe_ask()
386
386
  if is_mfa:
387
387
  config["two_factor_auth"] = is_mfa
388
388
 
@@ -393,11 +393,13 @@ def server_configuration_questionaire(instance_name: str) -> dict:
393
393
  "server url. If you are running a production server, this is the "
394
394
  "url that users will connect to.",
395
395
  default=current_server_url,
396
- ).ask()
396
+ ).unsafe_ask()
397
397
 
398
- is_add_vpn = q.confirm("Do you want to add a VPN server?", default=False).ask()
398
+ is_add_vpn = q.confirm(
399
+ "Do you want to add a VPN server?", default=False
400
+ ).unsafe_ask()
399
401
  if is_add_vpn:
400
- vpn_config = q.prompt(
402
+ vpn_config = q.unsafe_prompt(
401
403
  [
402
404
  {
403
405
  "type": "text",
@@ -434,12 +436,14 @@ def server_configuration_questionaire(instance_name: str) -> dict:
434
436
  )
435
437
  config["vpn_server"] = vpn_config
436
438
 
437
- is_add_rabbitmq = q.confirm("Do you want to add a RabbitMQ message queue?").ask()
439
+ is_add_rabbitmq = q.confirm(
440
+ "Do you want to add a RabbitMQ message queue?"
441
+ ).unsafe_ask()
438
442
  if is_add_rabbitmq:
439
- rabbit_uri = q.text(message="Enter the URI for your RabbitMQ:").ask()
443
+ rabbit_uri = q.text(message="Enter the URI for your RabbitMQ:").unsafe_ask()
440
444
  run_rabbit_locally = q.confirm(
441
445
  "Do you want to run RabbitMQ locally? (Use only for testing)"
442
- ).ask()
446
+ ).unsafe_ask()
443
447
  config["rabbitmq"] = {
444
448
  "uri": rabbit_uri,
445
449
  "start_with_server": run_rabbit_locally,
@@ -449,7 +453,7 @@ def server_configuration_questionaire(instance_name: str) -> dict:
449
453
  is_add_community_store = q.confirm(
450
454
  "Do you want to make the algorithms from the community algorithm store "
451
455
  "available to your users?"
452
- ).ask()
456
+ ).unsafe_ask()
453
457
  algorithm_stores = []
454
458
  if is_add_community_store:
455
459
  algorithm_stores.append(
@@ -457,14 +461,14 @@ def server_configuration_questionaire(instance_name: str) -> dict:
457
461
  )
458
462
  add_more_stores = q.confirm(
459
463
  "Do you want to add more algorithm stores?", default=False
460
- ).ask()
464
+ ).unsafe_ask()
461
465
  while add_more_stores:
462
- store_name = q.text(message="Enter the name of the store:").ask()
463
- store_url = q.text(message="Enter the URL of the store:").ask()
466
+ store_name = q.text(message="Enter the name of the store:").unsafe_ask()
467
+ store_url = q.text(message="Enter the URL of the store:").unsafe_ask()
464
468
  algorithm_stores.append({"name": store_name, "url": store_url})
465
469
  add_more_stores = q.confirm(
466
470
  "Do you want to add more algorithm stores?", default=False
467
- ).ask()
471
+ ).unsafe_ask()
468
472
  config["algorithm_stores"] = algorithm_stores
469
473
 
470
474
  return config
@@ -496,12 +500,12 @@ def algo_store_configuration_questionaire(instance_name: str) -> dict:
496
500
  "What is the Vantage6 server linked to the algorithm store? "
497
501
  "Provide the link to the server endpoint.",
498
502
  default=default_v6_server_uri,
499
- ).ask()
503
+ ).unsafe_ask()
500
504
 
501
505
  root_username = q.text(
502
506
  "What is the username of the root user?",
503
507
  default=default_root_username,
504
- ).ask()
508
+ ).unsafe_ask()
505
509
 
506
510
  config["root_user"] = {
507
511
  "v6_server_uri": v6_server_uri,
@@ -514,7 +518,7 @@ def algo_store_configuration_questionaire(instance_name: str) -> dict:
514
518
  "Do you want to open the algorithm store to the public? This will allow anyone "
515
519
  "to view the algorithms, but they cannot modify them.",
516
520
  default=False,
517
- ).ask()
521
+ ).unsafe_ask()
518
522
  if is_open:
519
523
  open_algos_policy = "public"
520
524
  else:
@@ -523,7 +527,7 @@ def algo_store_configuration_questionaire(instance_name: str) -> dict:
523
527
  "the algorithms in the store? If not allowing this, you will have to assign"
524
528
  " the appropriate permissions to each user individually.",
525
529
  default=True,
526
- ).ask()
530
+ ).unsafe_ask()
527
531
  open_algos_policy = "whitelisted" if is_open_to_whitelist else "private"
528
532
  config["policies"]["algorithm_view"] = open_algos_policy
529
533
 
@@ -612,4 +616,10 @@ def select_configuration_questionaire(type_: InstanceType, system_folders: bool)
612
616
  raise Exception("No configurations could be found!")
613
617
 
614
618
  # pop the question
615
- return q.select("Select the configuration you want to use:", choices=choices).ask()
619
+ try:
620
+ return q.select(
621
+ "Select the configuration you want to use:", choices=choices
622
+ ).unsafe_ask()
623
+ except KeyboardInterrupt:
624
+ error("Aborted by user!")
625
+ exit(1)
@@ -8,7 +8,7 @@ from jinja2 import Environment, FileSystemLoader
8
8
  from colorama import Fore, Style
9
9
 
10
10
  from vantage6.common.globals import APPNAME, InstanceType, Ports
11
- from vantage6.common import info, error, generate_apikey
11
+ from vantage6.common import ensure_config_dir_writable, info, error, generate_apikey
12
12
 
13
13
  import vantage6.cli.dev.data as data_dir
14
14
  from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
@@ -114,13 +114,15 @@ def create_node_config_file(
114
114
  }
115
115
  )
116
116
 
117
- try:
118
- with open(full_path, "x") as f:
119
- f.write(node_config)
120
- except Exception as e:
121
- error(f"Could not write node configuration file: {e}")
117
+ # Check that we can write the node config
118
+ if not ensure_config_dir_writable():
119
+ error("Cannot write configuration file. Exiting...")
122
120
  exit(1)
123
121
 
122
+ Path(full_path).parent.mkdir(parents=True, exist_ok=True)
123
+ with open(full_path, "x", encoding="utf-8") as f:
124
+ f.write(node_config)
125
+
124
126
  info(
125
127
  f"Spawned node for organization {Fore.GREEN}{config['org_id']}"
126
128
  f"{Style.RESET_ALL}"
@@ -4,9 +4,12 @@ from shutil import rmtree
4
4
  from pathlib import Path
5
5
 
6
6
  import click
7
+ import docker
8
+ from colorama import Fore, Style
7
9
 
8
10
  from vantage6.cli.context.algorithm_store import AlgorithmStoreContext
9
- from vantage6.common import info
11
+ from vantage6.common import info, error
12
+ from vantage6.common.globals import APPNAME
10
13
  from vantage6.cli.context.server import ServerContext
11
14
  from vantage6.cli.context.node import NodeContext
12
15
  from vantage6.cli.server.remove import cli_server_remove
@@ -34,6 +37,20 @@ def remove_demo_network(
34
37
  """
35
38
  ctx = get_dev_server_context(config, name)
36
39
 
40
+ # check that the server is not running
41
+ client = docker.from_env()
42
+ running_servers = client.containers.list(
43
+ filters={"label": f"{APPNAME}-type={InstanceType.SERVER}"}
44
+ )
45
+ running_server_names = [server.name for server in running_servers]
46
+ container_name = f"{APPNAME}-{name}-user-{InstanceType.SERVER}"
47
+ if container_name in running_server_names:
48
+ error(
49
+ f"Server {Fore.RED}{name}{Style.RESET_ALL} is still running! First stop "
50
+ "the network with 'v6 dev stop-demo-network'."
51
+ )
52
+ return
53
+
37
54
  # remove the server
38
55
  for handler in itertools.chain(ctx.log.handlers, ctx.log.root.handlers):
39
56
  handler.close()
@@ -45,9 +45,13 @@ def cli_node_attach(name: str, system_folders: bool) -> None:
45
45
  return
46
46
 
47
47
  if not name:
48
- name = q.select(
49
- "Select the node you wish to attach:", choices=running_node_names
50
- ).ask()
48
+ try:
49
+ name = q.select(
50
+ "Select the node you wish to attach:", choices=running_node_names
51
+ ).unsafe_ask()
52
+ except KeyboardInterrupt:
53
+ error("Aborted by user!")
54
+ return
51
55
  else:
52
56
  post_fix = "system" if system_folders else "user"
53
57
  name = f"{APPNAME}-{name}-{post_fix}"
@@ -26,8 +26,12 @@ def cli_node_clean() -> None:
26
26
  msg += volume.name + ","
27
27
  info(msg)
28
28
 
29
- confirm = q.confirm("Are you sure?")
30
- if confirm.ask():
29
+ try:
30
+ confirm = q.confirm("Are you sure?").unsafe_ask()
31
+ except KeyboardInterrupt:
32
+ confirm = False
33
+
34
+ if confirm:
31
35
  for volume in candidates:
32
36
  try:
33
37
  volume.remove()
@@ -58,9 +58,11 @@ def create_client_and_authenticate(
58
58
  """
59
59
  client = create_client(ctx)
60
60
 
61
- username = q.text("Username:").ask()
62
- password = q.password("Password:").ask()
63
- mfa_code = q.text("MFA code:").ask() if ask_mfa else None
61
+ try:
62
+ username, password, mfa_code = _get_auth_data()
63
+ except KeyboardInterrupt:
64
+ error("Authentication aborted.")
65
+ exit(1)
64
66
 
65
67
  try:
66
68
  client.authenticate(username, password, mfa_code=mfa_code)
@@ -73,6 +75,21 @@ def create_client_and_authenticate(
73
75
  return client
74
76
 
75
77
 
78
+ def _get_auth_data() -> tuple[str, str, str]:
79
+ """
80
+ Get authentication data from the user.
81
+
82
+ Returns
83
+ -------
84
+ tuple[str, str, str]
85
+ Tuple containing username, password and MFA code
86
+ """
87
+ username = q.text("Username:").unsafe_ask()
88
+ password = q.password("Password:").unsafe_ask()
89
+ mfa_code = q.text("MFA code:").unsafe_ask()
90
+ return username, password, mfa_code
91
+
92
+
76
93
  def select_node(name: str, system_folders: bool) -> tuple[str, str]:
77
94
  """
78
95
  Let user select node through questionnaire if name is not given.
@@ -47,7 +47,11 @@ def cli_node_new_configuration(name: str, system_folders: bool) -> None:
47
47
 
48
48
  # create config in ctx location
49
49
  flag = "--system" if system_folders else ""
50
- cfg_file = configuration_wizard(InstanceType.NODE, name, system_folders)
50
+ try:
51
+ cfg_file = configuration_wizard(InstanceType.NODE, name, system_folders)
52
+ except KeyboardInterrupt:
53
+ error("Configuration creation aborted.")
54
+ exit(1)
51
55
  info(f"New configuration created: {Fore.GREEN}{cfg_file}{Style.RESET_ALL}")
52
56
  info(
53
57
  f"You can start the node by running "
@@ -92,9 +92,13 @@ def cli_node_restart(
92
92
  ]
93
93
  else:
94
94
  if not name:
95
- container_name = q.select(
96
- "Select the node you wish to restart:", choices=running_node_names
97
- ).ask()
95
+ try:
96
+ container_name = q.select(
97
+ "Select the node you wish to restart:", choices=running_node_names
98
+ ).unsafe_ask()
99
+ except KeyboardInterrupt:
100
+ error("Aborted by user!")
101
+ return
98
102
  names = [get_name_from_container_name(container_name)]
99
103
  else:
100
104
  names = [name]
@@ -38,7 +38,11 @@ def cli_node_set_api_key(name: str, api_key: str, system_folders: bool) -> None:
38
38
  exit(1)
39
39
 
40
40
  if not api_key:
41
- api_key = q.text("Please enter your new API key:").ask()
41
+ try:
42
+ api_key = q.text("Please enter your new API key:").unsafe_ask()
43
+ except KeyboardInterrupt:
44
+ error("API key input aborted.")
45
+ exit(1)
42
46
 
43
47
  # get configuration manager
44
48
  ctx = NodeContext(name, system_folders=system_folders)
@@ -235,15 +235,23 @@ def cli_node_start(
235
235
  label_capitals = label.upper()
236
236
 
237
237
  try:
238
- file_based = Path(uri).exists()
238
+ db_file_exists = Path(uri).exists()
239
239
  except Exception:
240
240
  # If the database uri cannot be parsed, it is definitely not a
241
241
  # file. In case of http servers or sql servers, checking the path
242
242
  # of the the uri will lead to an OS-dependent error, which is why
243
243
  # we catch all exceptions here.
244
- file_based = False
244
+ db_file_exists = False
245
245
 
246
- if not file_based and not force_db_mount:
246
+ if db_type in ["folder", "csv", "parquet", "excel"] and not db_file_exists:
247
+ error(
248
+ f"Database {Fore.RED}{uri}{Style.RESET_ALL} not found. Databases of "
249
+ f"type '{db_type}' must be present on the harddrive. Please update "
250
+ "your node configuration file."
251
+ )
252
+ exit(1)
253
+
254
+ if not db_file_exists and not force_db_mount:
247
255
  debug(" - non file-based database added")
248
256
  env[f"{label_capitals}_DATABASE_URI"] = uri
249
257
  else:
@@ -69,9 +69,13 @@ def cli_node_stop(
69
69
  _stop_node(client, container_name, force, system_folders)
70
70
  else:
71
71
  if not name:
72
- container_name = q.select(
73
- "Select the node you wish to stop:", choices=running_node_names
74
- ).ask()
72
+ try:
73
+ container_name = q.select(
74
+ "Select the node you wish to stop:", choices=running_node_names
75
+ ).unsafe_ask()
76
+ except KeyboardInterrupt:
77
+ error("Aborted by user!")
78
+ return
75
79
  else:
76
80
  post_fix = "system" if system_folders else "user"
77
81
  container_name = f"{APPNAME}-{name}-{post_fix}"
@@ -42,9 +42,13 @@ def cli_node_version(name: str, system_folders: bool) -> None:
42
42
  "nodes that are running"
43
43
  )
44
44
  exit(1)
45
- name = q.select(
46
- "Select the node you wish to inspect:", choices=running_node_names
47
- ).ask()
45
+ try:
46
+ name = q.select(
47
+ "Select the node you wish to inspect:", choices=running_node_names
48
+ ).unsafe_ask()
49
+ except KeyboardInterrupt:
50
+ error("Aborted by user!")
51
+ return
48
52
  else:
49
53
  post_fix = "system" if system_folders else "user"
50
54
  name = f"{APPNAME}-{name}-{post_fix}"
@@ -34,9 +34,13 @@ def cli_server_attach(name: str, system_folders: bool) -> None:
34
34
  running_server_names = [node.name for node in running_servers]
35
35
 
36
36
  if not name:
37
- name = q.select(
38
- "Select the server you wish to attach:", choices=running_server_names
39
- ).ask()
37
+ try:
38
+ name = q.select(
39
+ "Select the server you wish to attach:", choices=running_server_names
40
+ ).unsafe_ask()
41
+ except KeyboardInterrupt:
42
+ error("Aborted by user!")
43
+ return
40
44
  else:
41
45
  post_fix = "system" if system_folders else "user"
42
46
  name = f"{APPNAME}-{name}-{post_fix}-{InstanceType.SERVER}"
@@ -45,7 +45,11 @@ def cli_server_new(name: str, system_folders: bool) -> None:
45
45
  exit(1)
46
46
 
47
47
  # create config in ctx location
48
- cfg_file = configuration_wizard(InstanceType.SERVER, name, system_folders)
48
+ try:
49
+ cfg_file = configuration_wizard(InstanceType.SERVER, name, system_folders)
50
+ except KeyboardInterrupt:
51
+ error("Configuration creation aborted.")
52
+ exit(1)
49
53
  info(f"New configuration created: {Fore.GREEN}{cfg_file}{Style.RESET_ALL}")
50
54
 
51
55
  # info(f"root user created.")
@@ -131,7 +131,7 @@ def cli_server_start(
131
131
  )
132
132
  else:
133
133
  warning(
134
- "Message queue disabled! This means that the vantage6 server "
134
+ "Message queue is not set up! This means that the vantage6 server "
135
135
  "cannot be scaled horizontally!"
136
136
  )
137
137
 
@@ -53,9 +53,13 @@ def cli_server_stop(name: str, system_folders: bool, all_servers: bool):
53
53
 
54
54
  # make sure we have a configuration name to work with
55
55
  if not name:
56
- container_name = q.select(
57
- "Select the server you wish to stop:", choices=running_server_names
58
- ).ask()
56
+ try:
57
+ container_name = q.select(
58
+ "Select the server you wish to stop:", choices=running_server_names
59
+ ).unsafe_ask()
60
+ except KeyboardInterrupt:
61
+ error("Aborted by user!")
62
+ return
59
63
  else:
60
64
  post_fix = "system" if system_folders else "user"
61
65
  container_name = f"{APPNAME}-{name}-{post_fix}-{InstanceType.SERVER}"
@@ -16,13 +16,13 @@ from vantage6.cli.test.common.diagnostic_runner import DiagnosticRunner
16
16
  @click.option(
17
17
  "--username",
18
18
  type=str,
19
- default="root",
19
+ default="dev_admin",
20
20
  help="Username of vantage6 user account to create the task with",
21
21
  )
22
22
  @click.option(
23
23
  "--password",
24
24
  type=str,
25
- default="root",
25
+ default="password",
26
26
  help="Password of vantage6 user account to create the task with",
27
27
  )
28
28
  @click.option(
@@ -56,6 +56,12 @@ from vantage6.cli.test.common.diagnostic_runner import DiagnosticRunner
56
56
  default=None,
57
57
  help="Path to the private key for end-to-end encryption",
58
58
  )
59
+ @click.option(
60
+ "--mfa-code",
61
+ type=str,
62
+ help="Multi-factor authentication code. Use this if MFA is enabled on the "
63
+ "server.",
64
+ )
59
65
  def cli_test_features(
60
66
  host: str,
61
67
  port: int,
@@ -68,6 +74,7 @@ def cli_test_features(
68
74
  online_only: bool,
69
75
  no_vpn: bool,
70
76
  private_key: str | None,
77
+ mfa_code: str | None,
71
78
  ) -> list[dict]:
72
79
  """
73
80
  Run diagnostic checks on an existing vantage6 network.
@@ -83,7 +90,7 @@ def cli_test_features(
83
90
  organizations = None
84
91
 
85
92
  client = UserClient(host=host, port=port, path=api_path, log_level="critical")
86
- client.authenticate(username=username, password=password)
93
+ client.authenticate(username=username, password=password, mfa_code=mfa_code)
87
94
  client.setup_encryption(private_key)
88
95
  diagnose = DiagnosticRunner(client, collaboration, organizations, online_only)
89
96
  res = diagnose(base=True, vpn=not no_vpn)
@@ -90,7 +90,11 @@ def prompt_config_name(name: str | None) -> None:
90
90
  The name of the configuration
91
91
  """
92
92
  if not name:
93
- name = q.text("Please enter a configuration-name:").ask()
93
+ try:
94
+ name = q.text("Please enter a configuration-name:").unsafe_ask()
95
+ except KeyboardInterrupt:
96
+ error("Aborted by user!")
97
+ exit(1)
94
98
  if name.count(" ") > 0:
95
99
  name = name.replace(" ", "-")
96
100
  info(f"Replaced spaces from configuration name: {name}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vantage6
3
- Version: 4.9.1
3
+ Version: 4.10.0rc2
4
4
  Summary: vantage6 command line interface
5
5
  Home-page: https://github.com/vantage6/vantage6
6
6
  Requires-Python: >=3.10
@@ -9,8 +9,8 @@ questionary==1.10.0
9
9
  rich==13.5.2
10
10
  schema==0.7.5
11
11
  SQLAlchemy==1.4.46
12
- vantage6-common==4.9.1
13
- vantage6-client==4.9.1
12
+ vantage6-common==4.10.0rc2
13
+ vantage6-client==4.10.0rc2
14
14
 
15
15
  [dev]
16
16
  coverage==6.4.4
@@ -1 +0,0 @@
1
- 0
@@ -1,3 +0,0 @@
1
- """ Command line interface for the vantage6 infrastructure. """
2
-
3
- from ._version import version_info, __version__ # noqa: F401
File without changes
File without changes