vantage6 4.5.2__tar.gz → 4.13.3rc2__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.
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/PKG-INFO +70 -15
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/setup.py +13 -8
- vantage6-4.13.3rc2/tests_cli/test_client_script.py +23 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/tests_cli/test_node_cli.py +40 -7
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/tests_cli/test_server_cli.py +4 -4
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/tests_cli/test_wizard.py +16 -7
- vantage6-4.13.3rc2/vantage6/cli/__build__ +1 -0
- vantage6-4.13.3rc2/vantage6/cli/__init__.py +3 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/_version.py +1 -1
- vantage6-4.13.3rc2/vantage6/cli/algorithm/create.py +193 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/algorithm/update.py +5 -1
- vantage6-4.13.3rc2/vantage6/cli/algostore/attach.py +49 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/algostore/list.py +1 -1
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/algostore/new.py +9 -3
- vantage6-4.13.3rc2/vantage6/cli/algostore/remove.py +52 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/algostore/start.py +11 -7
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/algostore/stop.py +9 -9
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/cli.py +6 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/common/start.py +92 -2
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/common/utils.py +37 -7
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/configuration_manager.py +2 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/configuration_wizard.py +185 -102
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/context/algorithm_store.py +1 -1
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/context/node.py +9 -5
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/context/server.py +26 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/dev/create.py +282 -60
- vantage6-4.13.3rc2/vantage6/cli/dev/data/km_dataset.csv +2401 -0
- vantage6-4.13.3rc2/vantage6/cli/dev/data/olympic_athletes_2016.csv +2425 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/dev/remove.py +54 -6
- vantage6-4.13.3rc2/vantage6/cli/dev/start.py +123 -0
- vantage6-4.13.3rc2/vantage6/cli/dev/stop.py +47 -0
- vantage6-4.13.3rc2/vantage6/cli/dev/utils.py +24 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/globals.py +15 -5
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/node/attach.py +7 -3
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/node/clean.py +9 -3
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/node/common/__init__.py +27 -5
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/node/create_private_key.py +10 -2
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/node/new.py +7 -3
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/node/remove.py +4 -5
- vantage6-4.13.3rc2/vantage6/cli/node/restart.py +128 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/node/set_api_key.py +7 -3
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/node/start.py +21 -9
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/node/stop.py +7 -3
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/node/version.py +7 -3
- vantage6-4.13.3rc2/vantage6/cli/prometheus/monitoring_manager.py +146 -0
- vantage6-4.13.3rc2/vantage6/cli/prometheus/prometheus.yml +5 -0
- vantage6-4.13.3rc2/vantage6/cli/rabbitmq/__init__.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/rabbitmq/queue_manager.py +2 -2
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/server/attach.py +9 -5
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/server/import_.py +6 -3
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/server/list.py +1 -1
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/server/new.py +7 -3
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/server/remove.py +14 -4
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/server/shell.py +1 -1
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/server/start.py +72 -11
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/server/stop.py +17 -6
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/server/version.py +2 -2
- vantage6-4.13.3rc2/vantage6/cli/template/algo_store_config.j2 +23 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/template/node_config.j2 +15 -2
- vantage6-4.13.3rc2/vantage6/cli/template/server_config.j2 +35 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/template/server_import_config.j2 +3 -4
- vantage6-4.13.3rc2/vantage6/cli/test/algo_test_scripts/algo_test_arguments.py +33 -0
- vantage6-4.13.3rc2/vantage6/cli/test/algo_test_scripts/algo_test_script.py +88 -0
- vantage6-4.13.3rc2/vantage6/cli/test/client_script.py +150 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/test/feature_tester.py +14 -4
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/test/integration_test.py +7 -7
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/utils.py +5 -1
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6.egg-info/PKG-INFO +70 -15
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6.egg-info/SOURCES.txt +12 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6.egg-info/requires.txt +6 -5
- vantage6-4.5.2/vantage6/cli/__build__ +0 -1
- vantage6-4.5.2/vantage6/cli/__init__.py +0 -3
- vantage6-4.5.2/vantage6/cli/algorithm/create.py +0 -51
- vantage6-4.5.2/vantage6/cli/algostore/attach.py +0 -32
- vantage6-4.5.2/vantage6/cli/dev/start.py +0 -52
- vantage6-4.5.2/vantage6/cli/dev/stop.py +0 -32
- vantage6-4.5.2/vantage6/cli/rabbitmq/__init__.py +0 -28
- vantage6-4.5.2/vantage6/cli/template/server_config.j2 +0 -16
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/setup.cfg +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/tests_cli/__init__.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/tests_cli/test_example.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/algostore/files.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/common/decorator.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/context/__init__.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/context/base_server.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/node/files.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/node/list.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/rabbitmq/definitions.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/rabbitmq/rabbitmq.config +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/server/common/__init__.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/server/files.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6/cli/test/common/diagnostic_runner.py +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6.egg-info/dependency_links.txt +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6.egg-info/entry_points.txt +0 -0
- {vantage6-4.5.2 → vantage6-4.13.3rc2}/vantage6.egg-info/top_level.txt +0 -0
|
@@ -1,11 +1,34 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: vantage6
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.13.3rc2
|
|
4
4
|
Summary: vantage6 command line interface
|
|
5
5
|
Home-page: https://github.com/vantage6/vantage6
|
|
6
6
|
Requires-Python: >=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: click==8.1.3
|
|
9
|
+
Requires-Dist: colorama==0.4.6
|
|
10
|
+
Requires-Dist: copier==9.9.1
|
|
11
|
+
Requires-Dist: docker==7.1.0
|
|
12
|
+
Requires-Dist: ipython==8.10.0
|
|
13
|
+
Requires-Dist: jinja2==3.1.6
|
|
14
|
+
Requires-Dist: pandas>=1.5.3
|
|
15
|
+
Requires-Dist: questionary==2.1.1
|
|
16
|
+
Requires-Dist: rich==13.5.2
|
|
17
|
+
Requires-Dist: schema==0.7.5
|
|
18
|
+
Requires-Dist: SQLAlchemy==1.4.46
|
|
19
|
+
Requires-Dist: vantage6-common==4.13.3rc2
|
|
20
|
+
Requires-Dist: vantage6-client==4.13.3rc2
|
|
8
21
|
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: coverage==6.4.4; extra == "dev"
|
|
23
|
+
Requires-Dist: black; extra == "dev"
|
|
24
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
25
|
+
Dynamic: description
|
|
26
|
+
Dynamic: description-content-type
|
|
27
|
+
Dynamic: home-page
|
|
28
|
+
Dynamic: provides-extra
|
|
29
|
+
Dynamic: requires-dist
|
|
30
|
+
Dynamic: requires-python
|
|
31
|
+
Dynamic: summary
|
|
9
32
|
|
|
10
33
|
<h1 align="center">
|
|
11
34
|
<br>
|
|
@@ -23,6 +46,9 @@ Provides-Extra: dev
|
|
|
23
46
|
[](https://coveralls.io/github/vantage6/vantage6?branch=main)
|
|
24
47
|
[](https://app.codacy.com/gh/vantage6/vantage6/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
|
25
48
|
[](https://zenodo.org/badge/latestdoi/492818831)
|
|
49
|
+
[](https://discord.gg/yAyFf6Y)
|
|
50
|
+
[](https://research-software-directory.org/software/vantage6)
|
|
51
|
+
|
|
26
52
|
|
|
27
53
|
</h3>
|
|
28
54
|
|
|
@@ -30,26 +56,39 @@ Provides-Extra: dev
|
|
|
30
56
|
<a href="#books-quickstart">Quickstart</a> •
|
|
31
57
|
<a href="#project-structure">Project structure</a> •
|
|
32
58
|
<a href="#gift_heart-join-the-community">Join the community</a> •
|
|
59
|
+
<a href="#scroll-license">License</a> •
|
|
60
|
+
<a href="#black_nib-code-of-conduct">Code of conduct</a> •
|
|
33
61
|
<a href="#black_nib-references">References</a>
|
|
34
62
|
</p>
|
|
35
63
|
|
|
36
64
|
---
|
|
37
65
|
|
|
38
|
-
This repository is contains all the **vantage6** infrastructure source code. The **vantage6** technology enables to manage and deploy privacy enhancing technologies like Federated Learning (FL) and Multi-Party Computation (MPC). Please visit our [website
|
|
66
|
+
This repository is contains all the **vantage6** infrastructure source code. The **vantage6** technology enables to manage and deploy privacy enhancing technologies like Federated Learning (FL) and Multi-Party Computation (MPC). Please visit our [website](https://vantage6.ai) to learn more!
|
|
39
67
|
|
|
40
|
-
You can find more (user) documentation at [readthedocs
|
|
68
|
+
You can find more (user) documentation at [readthedocs](https://docs.vantage6.ai). If you have any questions, suggestions or just want to chat about federated learning: join our [Discord)](https://discord.gg/yAyFf6Y) channel.
|
|
41
69
|
|
|
42
70
|
## Infrastructure overview
|
|
43
71
|
|
|
44
72
|

|
|
45
73
|
|
|
46
|
-
_A High level overview of the vantage6 infrastructure. Vantage6 has both a
|
|
74
|
+
_A High level overview of the vantage6 infrastructure. Vantage6 has both a
|
|
75
|
+
client-server and peer-to-peer architecture. The client is used by the researcher to
|
|
76
|
+
create (PET) computation requests. It is also used to manage users, organizations and
|
|
77
|
+
collaborations. The server contains users, organizations, collaborations, tasks and
|
|
78
|
+
their results. It provides a central access point for both the clients and nodes. The
|
|
79
|
+
nodes have access to privacy sensitive data and handle computation requests retrieved
|
|
80
|
+
from the server. Computation request are executed as separate containers on the node.
|
|
81
|
+
These containers are connected to containers at other nodes by a VPN network._
|
|
47
82
|
|
|
48
83
|
## :books: Quickstart
|
|
49
84
|
|
|
50
85
|
### Requirements
|
|
51
86
|
|
|
52
|
-
The **vantage6** infrastructure is delivered in Docker images. To run these images, you
|
|
87
|
+
The **vantage6** infrastructure is delivered in Docker images. To run these images, you
|
|
88
|
+
need to have [Docker](https://docs.docker.com/get-docker/) installed. To install the
|
|
89
|
+
latest version of the vantage6 CLI, you need to have
|
|
90
|
+
[Python](https://www.python.org/downloads/), we recommend using an environment manager
|
|
91
|
+
like [mini-conda](https://docs.conda.io/en/latest/miniconda.html).
|
|
53
92
|
|
|
54
93
|
Install the latest version of the vantage6 CLI by using:
|
|
55
94
|
|
|
@@ -85,9 +124,11 @@ v6 node attach
|
|
|
85
124
|
v6 server attach
|
|
86
125
|
```
|
|
87
126
|
|
|
88
|
-
From here you can use the [vantage6-client](https://pypi.org/project/vantage6-client)
|
|
127
|
+
From here you can use the [vantage6-client](https://pypi.org/project/vantage6-client)
|
|
128
|
+
to interact with the server. The demo network has a pre-configured organization with
|
|
129
|
+
the following credentials:
|
|
89
130
|
|
|
90
|
-
- Username: `
|
|
131
|
+
- Username: `dev_admin`
|
|
91
132
|
- Password: `password`
|
|
92
133
|
|
|
93
134
|
For example, you can create a new organization by running:
|
|
@@ -95,8 +136,8 @@ For example, you can create a new organization by running:
|
|
|
95
136
|
```python
|
|
96
137
|
from vantage6.client import Client
|
|
97
138
|
|
|
98
|
-
client = Client('http://127.0.0.1',
|
|
99
|
-
client.authenticate('
|
|
139
|
+
client = Client('http://127.0.0.1', 7601, '/api', log_level='debug')
|
|
140
|
+
client.authenticate('dev_admin', 'password')
|
|
100
141
|
client.setup_encryption(None)
|
|
101
142
|
|
|
102
143
|
client.organization.create(
|
|
@@ -109,7 +150,7 @@ client.organization.create(
|
|
|
109
150
|
)
|
|
110
151
|
```
|
|
111
152
|
|
|
112
|
-
You can find more (user) documentation at [readthedocs
|
|
153
|
+
You can find more (user) documentation at [readthedocs](https://docs.vantage6.ai)
|
|
113
154
|
|
|
114
155
|
## Project structure
|
|
115
156
|
|
|
@@ -134,14 +175,16 @@ easily.
|
|
|
134
175
|
|
|
135
176
|
### Docker images
|
|
136
177
|
|
|
137
|
-
The vantage6 infrastructure is delivered in Docker images. All Docker images are stored
|
|
178
|
+
The vantage6 infrastructure is delivered in Docker images. All Docker images are stored
|
|
179
|
+
in our private [Harbor](https://goharbor.io/) registry. The most important images are:
|
|
138
180
|
|
|
139
181
|
- `harbor2.vantage6.ai/infrastructure/node:VERSION` -> _Node application Docker image_
|
|
140
182
|
- `harbor2.vantage6.ai/infrastructure/server:VERSION` -> _Server application Docker image_
|
|
141
183
|
- `harbor2.vantage6.ai/infrastructure/ui:VERSION` -> _User interface Docker image_
|
|
142
184
|
- `harbor2.vantage6.ai/infrastructure/algorithm-store:VERSION` -> _Algorithm store Docker image_
|
|
143
185
|
|
|
144
|
-
with `VERSION` being the full semantic version of the vantage6 infrastructure, e.g.
|
|
186
|
+
with `VERSION` being the full semantic version of the vantage6 infrastructure, e.g.
|
|
187
|
+
`4.0.0` or `4.1.0rc0`.
|
|
145
188
|
|
|
146
189
|
Several other images are used to support the infrastructure:
|
|
147
190
|
|
|
@@ -159,12 +202,24 @@ And finally there are some images released for algorithm development:
|
|
|
159
202
|
|
|
160
203
|
## :gift_heart: Join the community!
|
|
161
204
|
|
|
162
|
-
We hope to continue developing, improving, and supporting **vantage6** with the help of
|
|
205
|
+
We hope to continue developing, improving, and supporting **vantage6** with the help of
|
|
206
|
+
the federated learning community. If you are interested in contributing, first of all,
|
|
207
|
+
thank you! Second, please take a look at our
|
|
208
|
+
[contributing guidelines](https://docs.vantage6.ai/en/main/devops/contribute.html)
|
|
209
|
+
and our [code of conduct](CODE_OF_CONDUCT.md).
|
|
163
210
|
|
|
164
211
|
<a href="https://github.com/vantage6/vantage6/graphs/contributors">
|
|
165
212
|
<img src="https://contrib.rocks/image?repo=vantage6/vantage6" />
|
|
166
213
|
</a>
|
|
167
214
|
|
|
215
|
+
## :scroll: License
|
|
216
|
+
|
|
217
|
+
This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
|
|
218
|
+
|
|
219
|
+
## :black_nib: Code of Conduct
|
|
220
|
+
|
|
221
|
+
Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). **By participating in any way in this project you agree to abide by its terms.**
|
|
222
|
+
|
|
168
223
|
## :black_nib: References
|
|
169
224
|
|
|
170
225
|
If you are using **vantage6**, please cite this repository as well as the accompanying papers as follows:
|
|
@@ -179,5 +234,5 @@ If you are using **vantage6**, please cite this repository as well as the accomp
|
|
|
179
234
|
<a href="https://vantage6.ai">vantage6.ai</a> •
|
|
180
235
|
<a href="https://discord.gg/yAyFf6Y">Discord</a> •
|
|
181
236
|
<a href="https://vantage6.discourse.group/">Discourse</a> •
|
|
182
|
-
<a href="https://docs.vantage6.ai">User documentation</a>
|
|
237
|
+
<a href="https://docs.vantage6.ai">User documentation</a>
|
|
183
238
|
</p>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import codecs
|
|
2
2
|
import os
|
|
3
|
-
|
|
4
|
-
from os import path
|
|
5
3
|
from codecs import open
|
|
6
|
-
from
|
|
4
|
+
from os import path
|
|
7
5
|
from pathlib import Path
|
|
8
6
|
|
|
7
|
+
from setuptools import find_namespace_packages, setup
|
|
8
|
+
|
|
9
9
|
# get current directory
|
|
10
10
|
here = Path(path.abspath(path.dirname(__file__)))
|
|
11
11
|
parent_dir = here.parent.absolute()
|
|
@@ -35,16 +35,17 @@ setup(
|
|
|
35
35
|
install_requires=[
|
|
36
36
|
"click==8.1.3",
|
|
37
37
|
"colorama==0.4.6",
|
|
38
|
-
"copier==9.
|
|
38
|
+
"copier==9.9.1",
|
|
39
39
|
"docker==7.1.0",
|
|
40
40
|
"ipython==8.10.0",
|
|
41
|
-
"jinja2==3.1.
|
|
42
|
-
"
|
|
41
|
+
"jinja2==3.1.6",
|
|
42
|
+
"pandas>=1.5.3",
|
|
43
|
+
"questionary==2.1.1",
|
|
43
44
|
"rich==13.5.2",
|
|
44
45
|
"schema==0.7.5",
|
|
45
46
|
"SQLAlchemy==1.4.46",
|
|
46
|
-
f
|
|
47
|
-
f
|
|
47
|
+
f"vantage6-common == {version_ns['__version__']}",
|
|
48
|
+
f"vantage6-client == {version_ns['__version__']}",
|
|
48
49
|
],
|
|
49
50
|
extras_require={
|
|
50
51
|
"dev": [
|
|
@@ -60,6 +61,10 @@ setup(
|
|
|
60
61
|
"template/node_config.j2",
|
|
61
62
|
"template/server_config.j2",
|
|
62
63
|
"template/server_import_config.j2",
|
|
64
|
+
"template/algo_store_config.j2",
|
|
65
|
+
"dev/data/olympic_athletes_2016.csv",
|
|
66
|
+
"dev/data/km_dataset.csv",
|
|
67
|
+
"prometheus/prometheus.yml",
|
|
63
68
|
],
|
|
64
69
|
},
|
|
65
70
|
entry_points={
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from click import UsageError
|
|
2
|
+
from vantage6.cli.test.client_script import cli_test_client_script
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestScriptTest(unittest.TestCase):
|
|
9
|
+
def test_script_incorrect_usage(self):
|
|
10
|
+
ctx = click.Context(cli_test_client_script)
|
|
11
|
+
|
|
12
|
+
with self.assertRaises(UsageError):
|
|
13
|
+
ctx.invoke(
|
|
14
|
+
cli_test_client_script,
|
|
15
|
+
script="path/to/script.py",
|
|
16
|
+
task_arguments="{'my_arg': 1}",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
with self.assertRaises(UsageError):
|
|
20
|
+
ctx.invoke(
|
|
21
|
+
cli_test_client_script,
|
|
22
|
+
task_arguments="not_a_json",
|
|
23
|
+
)
|
|
@@ -9,6 +9,7 @@ from io import BytesIO, StringIO
|
|
|
9
9
|
from click.testing import CliRunner
|
|
10
10
|
from docker.errors import APIError
|
|
11
11
|
|
|
12
|
+
from vantage6.common.globals import Ports
|
|
12
13
|
from vantage6.cli.globals import APPNAME
|
|
13
14
|
from vantage6.common import STRING_ENCODING
|
|
14
15
|
from vantage6.cli.common.utils import print_log_worker
|
|
@@ -16,6 +17,7 @@ from vantage6.cli.node.list import cli_node_list
|
|
|
16
17
|
from vantage6.cli.node.new import cli_node_new_configuration
|
|
17
18
|
from vantage6.cli.node.files import cli_node_files
|
|
18
19
|
from vantage6.cli.node.start import cli_node_start
|
|
20
|
+
from vantage6.cli.node.restart import cli_node_restart
|
|
19
21
|
from vantage6.cli.node.stop import cli_node_stop
|
|
20
22
|
from vantage6.cli.node.attach import cli_node_attach
|
|
21
23
|
from vantage6.cli.node.create_private_key import cli_node_create_private_key
|
|
@@ -84,7 +86,7 @@ class NodeCLITest(unittest.TestCase):
|
|
|
84
86
|
)
|
|
85
87
|
|
|
86
88
|
@patch("vantage6.cli.node.new.configuration_wizard")
|
|
87
|
-
@patch("vantage6.cli.node.new.
|
|
89
|
+
@patch("vantage6.cli.node.new.ensure_config_dir_writable")
|
|
88
90
|
@patch("vantage6.cli.node.common.NodeContext")
|
|
89
91
|
def test_new_config(self, context, permissions, wizard):
|
|
90
92
|
"""No error produced when creating new configuration."""
|
|
@@ -146,7 +148,7 @@ class NodeCLITest(unittest.TestCase):
|
|
|
146
148
|
# check non-zero exit code
|
|
147
149
|
self.assertEqual(result.exit_code, 1)
|
|
148
150
|
|
|
149
|
-
@patch("vantage6.cli.node.new.
|
|
151
|
+
@patch("vantage6.cli.node.new.ensure_config_dir_writable")
|
|
150
152
|
@patch("vantage6.cli.node.common.NodeContext")
|
|
151
153
|
def test_new_write_permissions(self, context, permissions):
|
|
152
154
|
"""User needs write permissions."""
|
|
@@ -207,7 +209,7 @@ class NodeCLITest(unittest.TestCase):
|
|
|
207
209
|
self.assertNotEqual(result.exit_code, 0)
|
|
208
210
|
|
|
209
211
|
@patch("docker.DockerClient.volumes")
|
|
210
|
-
@patch("vantage6.cli.node.start.
|
|
212
|
+
@patch("vantage6.cli.node.start.pull_infra_image")
|
|
211
213
|
@patch("vantage6.cli.common.decorator.get_context")
|
|
212
214
|
@patch("docker.DockerClient.containers")
|
|
213
215
|
@patch("vantage6.cli.node.start.check_docker_running", return_value=True)
|
|
@@ -242,19 +244,28 @@ class NodeCLITest(unittest.TestCase):
|
|
|
242
244
|
|
|
243
245
|
runner = CliRunner()
|
|
244
246
|
|
|
247
|
+
# Should fail when starting node with non-existing database CSV file
|
|
245
248
|
with runner.isolated_filesystem():
|
|
246
249
|
result = runner.invoke(cli_node_start, ["--name", "some-name"])
|
|
250
|
+
self.assertEqual(result.exit_code, 1)
|
|
247
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"])
|
|
248
256
|
self.assertEqual(result.exit_code, 0)
|
|
249
257
|
|
|
258
|
+
def _setup_stop_test(self, containers):
|
|
259
|
+
container1 = MagicMock()
|
|
260
|
+
container1.name = f"{APPNAME}-iknl-user"
|
|
261
|
+
containers.list.return_value = [container1]
|
|
262
|
+
|
|
250
263
|
@patch("docker.DockerClient.containers")
|
|
251
264
|
@patch("vantage6.cli.node.stop.check_docker_running", return_value=True)
|
|
252
265
|
@patch("vantage6.cli.node.stop.NodeContext")
|
|
253
266
|
@patch("vantage6.cli.node.stop.delete_volume_if_exists")
|
|
254
267
|
def test_stop(self, delete_volume, node_context, check_docker, containers):
|
|
255
|
-
|
|
256
|
-
container1.name = f"{APPNAME}-iknl-user"
|
|
257
|
-
containers.list.return_value = [container1]
|
|
268
|
+
self._setup_stop_test(containers)
|
|
258
269
|
|
|
259
270
|
runner = CliRunner()
|
|
260
271
|
|
|
@@ -266,6 +277,24 @@ class NodeCLITest(unittest.TestCase):
|
|
|
266
277
|
|
|
267
278
|
self.assertEqual(result.exit_code, 0)
|
|
268
279
|
|
|
280
|
+
@patch("docker.DockerClient.containers")
|
|
281
|
+
@patch("vantage6.cli.node.stop.check_docker_running", return_value=True)
|
|
282
|
+
@patch("vantage6.cli.node.stop.NodeContext")
|
|
283
|
+
@patch("vantage6.cli.node.stop.delete_volume_if_exists")
|
|
284
|
+
@patch("vantage6.cli.node.restart.subprocess.run")
|
|
285
|
+
def test_restart(
|
|
286
|
+
self, subprocess_run, delete_volume, node_context, check_docker, containers
|
|
287
|
+
):
|
|
288
|
+
"""Restart a node without errors."""
|
|
289
|
+
self._setup_stop_test(containers)
|
|
290
|
+
# The subprocess.run() function is called with the command to start the node.
|
|
291
|
+
# Unfortunately it is hard to test this, so we just return a successful run
|
|
292
|
+
subprocess_run.return_value = MagicMock(returncode=0)
|
|
293
|
+
runner = CliRunner()
|
|
294
|
+
with runner.isolated_filesystem():
|
|
295
|
+
result = runner.invoke(cli_node_restart, ["--name", "iknl"])
|
|
296
|
+
self.assertEqual(result.exit_code, 0)
|
|
297
|
+
|
|
269
298
|
@patch("vantage6.cli.node.attach.time")
|
|
270
299
|
@patch("vantage6.cli.node.attach.print_log_worker")
|
|
271
300
|
@patch("docker.DockerClient.containers")
|
|
@@ -408,7 +437,11 @@ class NodeCLITest(unittest.TestCase):
|
|
|
408
437
|
@patch("vantage6.cli.node.common.q")
|
|
409
438
|
def test_client(self, q, client, error, debug, info):
|
|
410
439
|
ctx = MagicMock(
|
|
411
|
-
config={
|
|
440
|
+
config={
|
|
441
|
+
"server_url": "localhost",
|
|
442
|
+
"port": Ports.DEV_SERVER.value,
|
|
443
|
+
"api_path": "",
|
|
444
|
+
}
|
|
412
445
|
)
|
|
413
446
|
|
|
414
447
|
# should not trigger an exception
|
|
@@ -18,7 +18,7 @@ class ServerCLITest(unittest.TestCase):
|
|
|
18
18
|
@patch("vantage6.cli.server.start.NetworkManager")
|
|
19
19
|
@patch("vantage6.cli.server.start.docker.types.Mount")
|
|
20
20
|
@patch("os.makedirs")
|
|
21
|
-
@patch("vantage6.cli.server.start.
|
|
21
|
+
@patch("vantage6.cli.server.start.pull_infra_image")
|
|
22
22
|
@patch("vantage6.cli.common.decorator.get_context")
|
|
23
23
|
@patch("vantage6.cli.server.start.docker.from_env")
|
|
24
24
|
@patch("vantage6.cli.common.start.check_docker_running", return_value=True)
|
|
@@ -112,7 +112,7 @@ class ServerCLITest(unittest.TestCase):
|
|
|
112
112
|
self.assertEqual(result.exit_code, 0)
|
|
113
113
|
|
|
114
114
|
@patch("vantage6.cli.server.new.configuration_wizard")
|
|
115
|
-
@patch("vantage6.cli.server.new.
|
|
115
|
+
@patch("vantage6.cli.server.new.ensure_config_dir_writable")
|
|
116
116
|
@patch("vantage6.cli.server.new.ServerContext")
|
|
117
117
|
def test_new(self, context, permissions, wizard):
|
|
118
118
|
"""New configuration without errors."""
|
|
@@ -132,7 +132,7 @@ class ServerCLITest(unittest.TestCase):
|
|
|
132
132
|
"""Stop server without errors."""
|
|
133
133
|
|
|
134
134
|
container1 = MagicMock()
|
|
135
|
-
container1.name = f"{APPNAME}-iknl-system-{InstanceType.SERVER}"
|
|
135
|
+
container1.name = f"{APPNAME}-iknl-system-{InstanceType.SERVER.value}"
|
|
136
136
|
containers.containers.list.return_value = [container1]
|
|
137
137
|
|
|
138
138
|
runner = CliRunner()
|
|
@@ -146,7 +146,7 @@ class ServerCLITest(unittest.TestCase):
|
|
|
146
146
|
def test_attach(self, containers, sleep):
|
|
147
147
|
"""Attach log to the console without errors."""
|
|
148
148
|
container1 = MagicMock()
|
|
149
|
-
container1.name = f"{APPNAME}-iknl-system-{InstanceType.SERVER}"
|
|
149
|
+
container1.name = f"{APPNAME}-iknl-system-{InstanceType.SERVER.value}"
|
|
150
150
|
containers.list.return_value = [container1]
|
|
151
151
|
|
|
152
152
|
sleep.side_effect = KeyboardInterrupt("Boom!")
|
|
@@ -9,7 +9,7 @@ from vantage6.cli.configuration_wizard import (
|
|
|
9
9
|
configuration_wizard,
|
|
10
10
|
select_configuration_questionaire,
|
|
11
11
|
)
|
|
12
|
-
from vantage6.common.globals import InstanceType
|
|
12
|
+
from vantage6.common.globals import InstanceType, NodePolicy
|
|
13
13
|
|
|
14
14
|
module_path = "vantage6.cli.configuration_wizard"
|
|
15
15
|
|
|
@@ -33,14 +33,20 @@ 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.
|
|
37
|
-
q.confirm.return_value.
|
|
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
|
|
41
41
|
True, # add algorithm policies
|
|
42
|
+
True, # add single algorithms to allowed_algorithms
|
|
42
43
|
"some-image", # algorithm image to whitelist
|
|
43
44
|
False, # don't add another algorithm image
|
|
45
|
+
True, # add algorithm stores to allowed_algorithm_stores
|
|
46
|
+
"some-store", # algorithm store to whitelist
|
|
47
|
+
False, # don't add another algorithm store
|
|
48
|
+
False, # answer question on combining policies on store level and
|
|
49
|
+
# single algorithm level
|
|
44
50
|
False, # don't abort if no server connection is made to pull
|
|
45
51
|
# collaboration settings
|
|
46
52
|
True, # Enable encryption
|
|
@@ -61,7 +67,10 @@ class WizardTest(unittest.TestCase):
|
|
|
61
67
|
]
|
|
62
68
|
for key in keys:
|
|
63
69
|
self.assertIn(key, config)
|
|
64
|
-
nested_keys = [
|
|
70
|
+
nested_keys = [
|
|
71
|
+
["policies", NodePolicy.ALLOWED_ALGORITHMS],
|
|
72
|
+
["policies", NodePolicy.ALLOWED_ALGORITHM_STORES],
|
|
73
|
+
]
|
|
65
74
|
for nesting in nested_keys:
|
|
66
75
|
current_config = config
|
|
67
76
|
for key in nesting:
|
|
@@ -70,8 +79,8 @@ class WizardTest(unittest.TestCase):
|
|
|
70
79
|
|
|
71
80
|
def test_server_wizard(self):
|
|
72
81
|
with patch(f"{module_path}.q") as q:
|
|
73
|
-
q.
|
|
74
|
-
q.confirm.return_value.
|
|
82
|
+
q.unsafe_prompt.side_effect = self.prompts
|
|
83
|
+
q.confirm.return_value.unsafe_ask.side_effect = [
|
|
75
84
|
True,
|
|
76
85
|
True,
|
|
77
86
|
True,
|
|
@@ -125,7 +134,7 @@ class WizardTest(unittest.TestCase):
|
|
|
125
134
|
available_configurations.return_value = [[config], []]
|
|
126
135
|
|
|
127
136
|
with patch(f"{module_path}.q") as q:
|
|
128
|
-
q.select.return_value.
|
|
137
|
+
q.select.return_value.unsafe_ask.return_value = "vtg6"
|
|
129
138
|
name = select_configuration_questionaire(InstanceType.NODE, True)
|
|
130
139
|
|
|
131
140
|
self.assertEqual(name, "vtg6")
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
2
|
|
@@ -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,
|
|
10
|
+
version_info = (4, 13, 3, "candidate", __build__, 0)
|
|
11
11
|
|
|
12
12
|
# Module version stage suffix map
|
|
13
13
|
_specifier_ = {"alpha": "a", "beta": "b", "candidate": "rc", "final": ""}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
import questionary as q
|
|
8
|
+
from copier import run_copy
|
|
9
|
+
|
|
10
|
+
from vantage6.common import error, warning
|
|
11
|
+
|
|
12
|
+
from vantage6.cli import __version__
|
|
13
|
+
from vantage6.cli.globals import ALGORITHM_TEMPLATE_REPO
|
|
14
|
+
from vantage6.cli.utils import info
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@click.command()
|
|
18
|
+
@click.option(
|
|
19
|
+
"-n", "--name", default=None, type=str, help="Name for your new algorithm"
|
|
20
|
+
)
|
|
21
|
+
@click.option(
|
|
22
|
+
"-d",
|
|
23
|
+
"--dir",
|
|
24
|
+
"directory",
|
|
25
|
+
default=None,
|
|
26
|
+
type=str,
|
|
27
|
+
help="Directory to put the algorithm into",
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
"--major-version",
|
|
31
|
+
default=None,
|
|
32
|
+
type=int,
|
|
33
|
+
help="Major version of the algorithm. By default, the current version is used.",
|
|
34
|
+
)
|
|
35
|
+
def cli_algorithm_create(name: str, directory: str, major_version: int | None) -> dict:
|
|
36
|
+
"""Creates a personalized template for a new algorithm
|
|
37
|
+
|
|
38
|
+
By answering a number of questions, a template will be created that will
|
|
39
|
+
simplify the creation of a new algorithm. The goal is that the algorithm
|
|
40
|
+
developer only focuses on the algorithm code rather than fitting it to
|
|
41
|
+
the vantage6 infrastructure.
|
|
42
|
+
|
|
43
|
+
The created template will contain a Python package with a Dockerfile that
|
|
44
|
+
can be used to build an appropriate Docker image that can be used as a
|
|
45
|
+
vantage6 algorithm.
|
|
46
|
+
"""
|
|
47
|
+
latest_tag_of_desired_major_version = None
|
|
48
|
+
if major_version is None:
|
|
49
|
+
major_version = int(__version__.split(".")[0])
|
|
50
|
+
latest_tag_of_desired_major_version = _get_latest_major_tag(major_version)
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
name, directory = _get_user_input(name, directory)
|
|
54
|
+
except KeyboardInterrupt:
|
|
55
|
+
info("Aborted by user!")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Create the template. The `unsafe` flag is used to allow running a Python script
|
|
59
|
+
# after creating the template that cleans up some things.
|
|
60
|
+
run_copy(
|
|
61
|
+
ALGORITHM_TEMPLATE_REPO,
|
|
62
|
+
directory,
|
|
63
|
+
data={"algorithm_name": name},
|
|
64
|
+
unsafe=True,
|
|
65
|
+
vcs_ref=latest_tag_of_desired_major_version,
|
|
66
|
+
)
|
|
67
|
+
info("Template created!")
|
|
68
|
+
info(f"You can find your new algorithm in: {directory}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _get_user_input(name: str, directory: str) -> None:
|
|
72
|
+
"""Get user input for the algorithm creation
|
|
73
|
+
|
|
74
|
+
Parameters
|
|
75
|
+
----------
|
|
76
|
+
name : str
|
|
77
|
+
Name for the new algorithm
|
|
78
|
+
directory : str
|
|
79
|
+
Directory to put the algorithm into
|
|
80
|
+
"""
|
|
81
|
+
if not name:
|
|
82
|
+
name = q.text("Name of your new algorithm:").unsafe_ask()
|
|
83
|
+
|
|
84
|
+
if not directory:
|
|
85
|
+
default_dir = str(Path(os.getcwd()) / name)
|
|
86
|
+
directory = q.text(
|
|
87
|
+
"Directory to put the algorithm in:", default=default_dir
|
|
88
|
+
).unsafe_ask()
|
|
89
|
+
return name, directory
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _get_latest_major_tag(major_version: int) -> str | None:
|
|
93
|
+
"""Get the latest tag for the given major version"""
|
|
94
|
+
# get the tags from the algorithm template repository
|
|
95
|
+
try:
|
|
96
|
+
tags = _get_algo_template_tags(ALGORITHM_TEMPLATE_REPO)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
error(f"Failed to fetch tags from {ALGORITHM_TEMPLATE_REPO}: {e}")
|
|
99
|
+
warning("Will use latest version instead.")
|
|
100
|
+
return None
|
|
101
|
+
# Filter tags for the given major version (e.g. 5.x.x)
|
|
102
|
+
tags_in_desired_major_version = [
|
|
103
|
+
tag
|
|
104
|
+
for tag in tags
|
|
105
|
+
if tag.startswith(f"{major_version}.")
|
|
106
|
+
and re.match(rf"^{major_version}\.\d+\.\d+", tag)
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
# sort the tags in descending order
|
|
110
|
+
tags_in_desired_major_version.sort(key=lambda s: _gen_sort_key(s), reverse=True)
|
|
111
|
+
return _first_non_prerelease_tag(tags_in_desired_major_version)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _first_non_prerelease_tag(tags: list[str]) -> str:
|
|
115
|
+
"""Return the first non-prerelease tag from a list of tags"""
|
|
116
|
+
for tag in tags:
|
|
117
|
+
patch = tag.split(".")[2]
|
|
118
|
+
try:
|
|
119
|
+
int(patch)
|
|
120
|
+
return tag
|
|
121
|
+
except ValueError:
|
|
122
|
+
continue
|
|
123
|
+
# no non-prerelease tag found - return first in the list (sorted in descending
|
|
124
|
+
# order)
|
|
125
|
+
return tags[0] if tags else None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _gen_sort_key(tag: str) -> list[int]:
|
|
129
|
+
"""Generate a sort key for a tag"""
|
|
130
|
+
major = int(tag.split(".")[0])
|
|
131
|
+
minor = int(tag.split(".")[1])
|
|
132
|
+
# Note: patch is not cast to int for sorting, because it may contain
|
|
133
|
+
# alpha/beta/rc suffixes
|
|
134
|
+
# TODO this will go wrong in sorting 1.2.13a1 vs 1.2.3a1, but we don't care
|
|
135
|
+
# about that for now, as it is unlikely that we have 10+ patch releases
|
|
136
|
+
# for the algorithm template repository
|
|
137
|
+
patch = tag.split(".")[2]
|
|
138
|
+
return [major, minor, patch]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _get_algo_template_tags(repo_url: str) -> list[str]:
|
|
142
|
+
"""Get all tags from a git repository
|
|
143
|
+
|
|
144
|
+
Parameters
|
|
145
|
+
----------
|
|
146
|
+
repo_url : str
|
|
147
|
+
Repository URL in format like "gh:owner/repo.git" or full git URL
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
list[str]
|
|
152
|
+
List of tag names (without refs/tags/ prefix)
|
|
153
|
+
"""
|
|
154
|
+
# Convert copier format (gh:owner/repo.git) to git URL
|
|
155
|
+
if repo_url.startswith("gh:"):
|
|
156
|
+
# Format: gh:owner/repo.git -> https://github.com/owner/repo.git
|
|
157
|
+
repo_path = repo_url[3:].rstrip(".git")
|
|
158
|
+
git_url = f"https://github.com/{repo_path}.git"
|
|
159
|
+
else:
|
|
160
|
+
git_url = repo_url
|
|
161
|
+
|
|
162
|
+
try:
|
|
163
|
+
# Use git ls-remote to fetch tags without cloning
|
|
164
|
+
result = subprocess.run(
|
|
165
|
+
["git", "ls-remote", "--tags", git_url],
|
|
166
|
+
capture_output=True,
|
|
167
|
+
text=True,
|
|
168
|
+
check=True,
|
|
169
|
+
timeout=10,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Parse output: lines look like "hash\trefs/tags/v1.0.0"
|
|
173
|
+
tags = []
|
|
174
|
+
for line in result.stdout.strip().split("\n"):
|
|
175
|
+
if line:
|
|
176
|
+
# Extract tag name from "refs/tags/tagname"
|
|
177
|
+
match = re.search(r"refs/tags/(.+)", line)
|
|
178
|
+
if match:
|
|
179
|
+
tag = match.group(1)
|
|
180
|
+
# Filter out ^{} suffix that git adds for annotated tags
|
|
181
|
+
if not tag.endswith("^{}"):
|
|
182
|
+
tags.append(tag)
|
|
183
|
+
|
|
184
|
+
return sorted(tags)
|
|
185
|
+
except subprocess.CalledProcessError as e:
|
|
186
|
+
info(f"Failed to fetch tags from {git_url}: {e}")
|
|
187
|
+
return []
|
|
188
|
+
except subprocess.TimeoutExpired:
|
|
189
|
+
info(f"Timeout while fetching tags from {git_url}")
|
|
190
|
+
return []
|
|
191
|
+
except FileNotFoundError:
|
|
192
|
+
info("git command not found. Please install git to fetch repository tags.")
|
|
193
|
+
return []
|