cvdlink 0.1.1__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.
- FeatureCloud/__init__.py +0 -0
- FeatureCloud/api/__init__.py +0 -0
- FeatureCloud/api/cli/__init__.py +0 -0
- FeatureCloud/api/cli/__main__.py +115 -0
- FeatureCloud/api/cli/app/__init__.py +0 -0
- FeatureCloud/api/cli/app/commands.py +182 -0
- FeatureCloud/api/cli/controller/__init__.py +0 -0
- FeatureCloud/api/cli/controller/commands.py +181 -0
- FeatureCloud/api/cli/test/__init__.py +0 -0
- FeatureCloud/api/cli/test/commands.py +251 -0
- FeatureCloud/api/cli/test/workflow/__init__.py +0 -0
- FeatureCloud/api/cli/test/workflow/commands.py +32 -0
- FeatureCloud/api/imp/__init__.py +0 -0
- FeatureCloud/api/imp/app/__init__.py +0 -0
- FeatureCloud/api/imp/app/commands.py +278 -0
- FeatureCloud/api/imp/controller/__init__.py +0 -0
- FeatureCloud/api/imp/controller/commands.py +246 -0
- FeatureCloud/api/imp/exceptions.py +29 -0
- FeatureCloud/api/imp/test/__init__.py +0 -0
- FeatureCloud/api/imp/test/api/__init__.py +0 -0
- FeatureCloud/api/imp/test/api/backend/__init__.py +0 -0
- FeatureCloud/api/imp/test/api/backend/auth.py +54 -0
- FeatureCloud/api/imp/test/api/backend/project.py +84 -0
- FeatureCloud/api/imp/test/api/controller.py +97 -0
- FeatureCloud/api/imp/test/commands.py +124 -0
- FeatureCloud/api/imp/test/helper.py +40 -0
- FeatureCloud/api/imp/util.py +45 -0
- FeatureCloud/app/__init__.py +0 -0
- FeatureCloud/app/api/__init__.py +0 -0
- FeatureCloud/app/api/http_ctrl.py +48 -0
- FeatureCloud/app/api/http_web.py +16 -0
- FeatureCloud/app/engine/__init__.py +0 -0
- FeatureCloud/app/engine/app.py +1214 -0
- FeatureCloud/app/engine/library.py +46 -0
- FeatureCloud/workflow/__init__.py +0 -0
- FeatureCloud/workflow/app.py +197 -0
- FeatureCloud/workflow/controller.py +17 -0
- FeatureCloud/workflow/example_wf.py +83 -0
- FeatureCloud/workflow/workflow.py +86 -0
- cvdlink-0.1.1.dist-info/METADATA +176 -0
- cvdlink-0.1.1.dist-info/RECORD +45 -0
- cvdlink-0.1.1.dist-info/WHEEL +5 -0
- cvdlink-0.1.1.dist-info/entry_points.txt +5 -0
- cvdlink-0.1.1.dist-info/licenses/LICENSE +201 -0
- cvdlink-0.1.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
import click
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
from FeatureCloud.api.imp.exceptions import FCException
|
|
8
|
+
|
|
9
|
+
from FeatureCloud.api.imp.test import commands
|
|
10
|
+
from FeatureCloud.api.cli.test.workflow.commands import workflow
|
|
11
|
+
from FeatureCloud.api.imp.test.helper import http
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.group("test")
|
|
15
|
+
def test() -> None:
|
|
16
|
+
"""Testbed related commands"""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
test.add_command(workflow)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@test.command('help')
|
|
23
|
+
def help():
|
|
24
|
+
_, msg = commands.help()
|
|
25
|
+
click.echo(msg)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@test.command('start')
|
|
29
|
+
@click.option('--controller-host', default='http://localhost:8000',
|
|
30
|
+
help='Address of your running controller instance (e.g. featurecloud test start --controller-host=http://localhost:8000).')
|
|
31
|
+
@click.option('--client-dirs', default='.,.',
|
|
32
|
+
help='Client directories separated by comma. The number of clients is based on the number of directories supplied here (e.g. `featurecloud test start --client-dirs=.,.,.,.` command will start 4 clients).',
|
|
33
|
+
required=True)
|
|
34
|
+
@click.option('--generic-dir', default='.',
|
|
35
|
+
help='Generic directory available for all clients. Content will be copied to the input folder of all '
|
|
36
|
+
'instances (e.g. featurecloud test start --generic-dir=.).',
|
|
37
|
+
required=True)
|
|
38
|
+
@click.option('--app-image', default='test_app',
|
|
39
|
+
help='The repository url of the app image (e.g. featurecloud test start --app-image=featurecloud.ai/test_app).',
|
|
40
|
+
required=True)
|
|
41
|
+
@click.option('--channel', default='local',
|
|
42
|
+
help='The communication channel to be used. Possible values: "local" or "internet" (e.g. featurecloud test start --channel=local).')
|
|
43
|
+
@click.option('--query-interval', default=2.0,
|
|
44
|
+
help='The interval after how many seconds the status call will be performed (e.g. featurecloud test start --query-interval=2).')
|
|
45
|
+
@click.option('--download-results',
|
|
46
|
+
help='A directory name where to download results. This will be created into /data/tests directory (e.g. featurecloud test start --download-results=./results).',
|
|
47
|
+
default='')
|
|
48
|
+
@click.option('--print-logs',
|
|
49
|
+
help='When selected, it will monitor the started test by printing the current status every 3s. When the test is finished (or has an error), the relevant logs will be output. Canceling the command after the test was started will NOT stop the test.',
|
|
50
|
+
is_flag=True)
|
|
51
|
+
def start(controller_host: str, client_dirs: str, generic_dir: str, app_image: str, channel: str, query_interval: str,
|
|
52
|
+
download_results: str, print_logs: bool):
|
|
53
|
+
"""Starts testbed run with the specified parameters"""
|
|
54
|
+
try:
|
|
55
|
+
test_id = commands.start(controller_host, client_dirs, generic_dir, app_image, channel, query_interval,
|
|
56
|
+
download_results)
|
|
57
|
+
click.echo(f"Test id={test_id} started")
|
|
58
|
+
except requests.exceptions.InvalidSchema:
|
|
59
|
+
click.echo(f'No connection adapters were found for {controller_host}')
|
|
60
|
+
return
|
|
61
|
+
except requests.exceptions.MissingSchema:
|
|
62
|
+
click.echo(f' Invalid URL {controller_host}: No scheme supplied. Perhaps you meant http://{controller_host}?')
|
|
63
|
+
return
|
|
64
|
+
except FCException as e:
|
|
65
|
+
click.echo(f'Error: {e}')
|
|
66
|
+
return
|
|
67
|
+
# start monitoring in case wanted (option --print-logs)
|
|
68
|
+
if print_logs:
|
|
69
|
+
while True:
|
|
70
|
+
time.sleep(5)
|
|
71
|
+
info = commands.info(controller_host, test_id)
|
|
72
|
+
status = info['status']
|
|
73
|
+
if len(status) != 1:
|
|
74
|
+
click.echo('monitoring failed, more than one status of the test found. Please check manually using featurecloud test logs')
|
|
75
|
+
return
|
|
76
|
+
status = status.iloc[0].strip()
|
|
77
|
+
# change format of starttime to be the same than used in controller logs
|
|
78
|
+
starttime = info['createdAt'].iloc[0].strip()
|
|
79
|
+
starttime = starttime[:10] + 'T' + starttime[11:]
|
|
80
|
+
starttime += 'Z'
|
|
81
|
+
click.echo(f'current Status: {status}')
|
|
82
|
+
if status in ['error', 'stopped', 'finished']:
|
|
83
|
+
# controller logs
|
|
84
|
+
response = http.get(url=f'{controller_host}/logs/?from={0}')
|
|
85
|
+
controllerString = "CONTROLLER"
|
|
86
|
+
click.echo(controllerString)
|
|
87
|
+
click.echo("="*len(controllerString))
|
|
88
|
+
if response.status_code == 200:
|
|
89
|
+
contlogs = response.json()
|
|
90
|
+
for logentry in contlogs:
|
|
91
|
+
timeentry = logentry['time']
|
|
92
|
+
if timeentry >= starttime:
|
|
93
|
+
click.echo(logentry)
|
|
94
|
+
click.echo('\n\n')
|
|
95
|
+
else:
|
|
96
|
+
click.echo("Could not get controller logs, use the frontend or featurecloud controller logs")
|
|
97
|
+
# logs of all clients
|
|
98
|
+
instances = commands.info(controller_host, test_id)['instances']
|
|
99
|
+
for inst in instances.iloc[0]:
|
|
100
|
+
inst_id = inst['id']
|
|
101
|
+
logs = commands.logs(controller_host, test_id, inst_id, '0')
|
|
102
|
+
clientString = f"CLIENT NUMBER {inst_id}"
|
|
103
|
+
click.echo(clientString)
|
|
104
|
+
click.echo('='*len(clientString))
|
|
105
|
+
click.echo('\n'.join(logs[-50:]) + '\n\n') # only show last 50 lines
|
|
106
|
+
return
|
|
107
|
+
elif status in ['running', 'writing']:
|
|
108
|
+
continue
|
|
109
|
+
else:
|
|
110
|
+
click.echo('Unknown status, stopping monitoring. Please check manually using featurecloud test logs or with the frontend')
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@test.command('stop')
|
|
116
|
+
@click.option('--controller-host', default='http://localhost:8000',
|
|
117
|
+
help='Http address of your running controller instance (e.g. featurecloud test stop --controller-host=http://localhost:8000).',
|
|
118
|
+
required=True)
|
|
119
|
+
@click.option('--test-id', help='The test id of the test to be stopped. The test id is returned by the start command (e.g.featurecloud test stop --test-id=1).')
|
|
120
|
+
def stop(controller_host: str, test_id: str or int):
|
|
121
|
+
'''Stops test with specified test id'''
|
|
122
|
+
try:
|
|
123
|
+
result = commands.stop(controller_host, test_id)
|
|
124
|
+
click.echo(f"Test id={result} stopped")
|
|
125
|
+
except requests.exceptions.InvalidSchema:
|
|
126
|
+
click.echo(f'No connection adapters were found for {controller_host}')
|
|
127
|
+
except requests.exceptions.MissingSchema:
|
|
128
|
+
click.echo(f' Invalid URL {controller_host}: No scheme supplied. Perhaps you meant http://{controller_host}?')
|
|
129
|
+
except FCException as e:
|
|
130
|
+
click.echo(f'Error: {e}')
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@test.command('delete')
|
|
134
|
+
@click.option('--controller-host', default='http://localhost:8000',
|
|
135
|
+
help='Address of your running controller instance. (e.g. featurecloud test delete all --controller-host=http://localhost:8000)',)
|
|
136
|
+
@click.option('--test-id', help='The test id of the test to be deleted. The test id is returned by the start command.'
|
|
137
|
+
'To delete all tests omit this option and use "delete all".')
|
|
138
|
+
@click.argument('all', type=str, nargs=1, required=False)
|
|
139
|
+
def delete(controller_host: str, test_id: str or int, all: str):
|
|
140
|
+
'''
|
|
141
|
+
Deletes test with specified id or alternatively, deletes all tests
|
|
142
|
+
|
|
143
|
+
ALL - delete all tests
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
|
|
147
|
+
featurecloud test delete --test-id=1
|
|
148
|
+
|
|
149
|
+
featurecloud test delete all
|
|
150
|
+
'''
|
|
151
|
+
try:
|
|
152
|
+
result = commands.delete(controller_host, test_id, all)
|
|
153
|
+
if all is not None:
|
|
154
|
+
if all.lower() == 'all':
|
|
155
|
+
click.echo(f"All tests deleted")
|
|
156
|
+
else:
|
|
157
|
+
click.echo(f'Wrong parameter {all}')
|
|
158
|
+
else:
|
|
159
|
+
click.echo(f"Test id={result} deleted")
|
|
160
|
+
except requests.exceptions.InvalidSchema:
|
|
161
|
+
click.echo(f'No connection adapters were found for {controller_host}')
|
|
162
|
+
except requests.exceptions.MissingSchema:
|
|
163
|
+
click.echo(f' Invalid URL {controller_host}: No scheme supplied. Perhaps you meant http://{controller_host}?')
|
|
164
|
+
except FCException as e:
|
|
165
|
+
click.echo(f'Error: {e}')
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@test.command('list')
|
|
169
|
+
@click.option('--controller-host', default='http://localhost:8000',
|
|
170
|
+
help='Address of your running controller instance (e.g. featurecloud test list --controller-host=http://localhost:8000).',
|
|
171
|
+
required=True)
|
|
172
|
+
@click.option('--format', help='Format of the test list. Possible options: json or dataframe (e.g. featurecloud test list --format=dataframe).', required=True, default='dataframe')
|
|
173
|
+
def list(controller_host: str, format: str):
|
|
174
|
+
'''List all tests'''
|
|
175
|
+
try:
|
|
176
|
+
result = commands.list(controller_host, format)
|
|
177
|
+
if len(result) == 0:
|
|
178
|
+
click.echo('No tests available')
|
|
179
|
+
else:
|
|
180
|
+
click.echo(result)
|
|
181
|
+
except requests.exceptions.InvalidSchema:
|
|
182
|
+
click.echo(f'No connection adapters were found for {controller_host}')
|
|
183
|
+
except requests.exceptions.MissingSchema:
|
|
184
|
+
click.echo(f' Invalid URL {controller_host}: No scheme supplied. Perhaps you meant http://{controller_host}?')
|
|
185
|
+
except FCException as e:
|
|
186
|
+
click.echo(f'Error: {e}')
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@test.command('info')
|
|
190
|
+
@click.option('--controller-host', default='http://localhost:8000',
|
|
191
|
+
help='Address of your running controller instance (e.g. featurecloud test info --controller-host=http://localhost:8000).',
|
|
192
|
+
required=True)
|
|
193
|
+
@click.option('--test-id', help='Test id to get info about (e.g. featurecloud test info --test-id=1).', required=True)
|
|
194
|
+
@click.option('--format', help='Format of the test info. Possible values: json or dataframe (e.g. featurecloud test info --format=dataframe).', required=True, default='dataframe')
|
|
195
|
+
def info(controller_host: str, test_id: str or int, format: str):
|
|
196
|
+
'''Get information about a running test'''
|
|
197
|
+
try:
|
|
198
|
+
result = commands.info(controller_host, test_id, format)
|
|
199
|
+
click.echo(result)
|
|
200
|
+
except requests.exceptions.InvalidSchema:
|
|
201
|
+
click.echo(f'No connection adapters were found for {controller_host}')
|
|
202
|
+
except requests.exceptions.MissingSchema:
|
|
203
|
+
click.echo(f' Invalid URL {controller_host}: No scheme supplied. Perhaps you meant http://{controller_host}?')
|
|
204
|
+
except FCException as e:
|
|
205
|
+
click.echo(f'Error: {e}')
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@test.command('traffic')
|
|
209
|
+
@click.option('--controller-host', default='http://localhost:8000',
|
|
210
|
+
help='Address of your running controller instance (e.g. featurecloud test traffic --controller-host=http://localhost:8000).',
|
|
211
|
+
required=True)
|
|
212
|
+
@click.option('--test-id', help='The test id to get traffic info about (e.g. featurecloud test traffic --test-id=1).')
|
|
213
|
+
@click.option('--format', help='Format of the test traffic. Possible values: json or dataframe (e.g. featurecloud test traffic --format=dataframe).e', required=True, default='dataframe')
|
|
214
|
+
def traffic(controller_host: str, test_id: str or int, format: str):
|
|
215
|
+
'''Displays traffic information inside tests'''
|
|
216
|
+
try:
|
|
217
|
+
result = commands.traffic(controller_host, test_id, format)
|
|
218
|
+
click.echo(result)
|
|
219
|
+
except requests.exceptions.InvalidSchema:
|
|
220
|
+
click.echo(f'No connection adapters were found for {controller_host}')
|
|
221
|
+
except requests.exceptions.MissingSchema:
|
|
222
|
+
click.echo(f' Invalid URL {controller_host}: No scheme supplied. Perhaps you meant http://{controller_host}?')
|
|
223
|
+
except FCException as e:
|
|
224
|
+
click.echo(f'Error: {e}')
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@test.command('logs')
|
|
228
|
+
@click.option('--controller-host', default='http://localhost:8000',
|
|
229
|
+
help='Address of your running controller instance (e.g. featurecloud test logs --controller-host=http://localhost:8000).',
|
|
230
|
+
required=True)
|
|
231
|
+
@click.option('--test-id', help='The test id to get logs about (e.g. featurecloud test logs --test-id=1).', required=True)
|
|
232
|
+
@click.option('--instance-id', help='The instance id of the test client. Instance ids can be obtained by running the info command (e.g. featurecloud test logs --test-id=1 --instance-id=0).', required=True)
|
|
233
|
+
@click.option('--from-row', help='Get logs from a certain row number (e.g. featurecloud test logs --test-id=1 --instance-id=0 --from-row=0).', default='', required=True)
|
|
234
|
+
def logs(controller_host: str, test_id: str or int, instance_id: str or int, from_row: str):
|
|
235
|
+
'''Get logs from test client'''
|
|
236
|
+
try:
|
|
237
|
+
result = commands.logs(controller_host, test_id, instance_id, from_row)
|
|
238
|
+
log_lines = ""
|
|
239
|
+
for line in result:
|
|
240
|
+
log_lines += str(line) + os.linesep
|
|
241
|
+
click.echo(log_lines)
|
|
242
|
+
except requests.exceptions.InvalidSchema:
|
|
243
|
+
click.echo(f'No connection adapters were found for {controller_host}')
|
|
244
|
+
except requests.exceptions.MissingSchema:
|
|
245
|
+
click.echo(f' Invalid URL {controller_host}: No scheme supplied. Perhaps you meant http://{controller_host}?')
|
|
246
|
+
except FCException as e:
|
|
247
|
+
click.echo(f'Error: {e}')
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
if __name__ == "__main__":
|
|
251
|
+
test()
|
|
File without changes
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import click
|
|
2
|
+
import importlib
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@click.group("workflow")
|
|
7
|
+
def workflow() -> None:
|
|
8
|
+
"""Workflow related commands"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@workflow.command('start')
|
|
12
|
+
@click.option('--controller-host', default='http://localhost:8000',
|
|
13
|
+
help='Address of your running controller instance.',
|
|
14
|
+
required=True)
|
|
15
|
+
@click.option('--wf-dir', default='',
|
|
16
|
+
help='path to directory containing the workflow',
|
|
17
|
+
required=True)
|
|
18
|
+
@click.option('--wf-file', default='example_wf',
|
|
19
|
+
help='python file including the workflow',
|
|
20
|
+
required=True)
|
|
21
|
+
@click.option('--channel', default='local',
|
|
22
|
+
help='The communication channel to be used. Can be local or internet.',
|
|
23
|
+
required=True)
|
|
24
|
+
@click.option('--query-interval', default=1,
|
|
25
|
+
help='The interval after how many seconds the status call will be performed.',
|
|
26
|
+
required=True)
|
|
27
|
+
def start_workflow(controller_host: str, wf_dir: str, wf_file: str, channel: str, query_interval: str):
|
|
28
|
+
sys.path.append(wf_dir)
|
|
29
|
+
workflow_class = importlib.import_module(wf_file)
|
|
30
|
+
wf = workflow_class.WorkFlow(controller_host=controller_host, channel=channel, query_interval=query_interval)
|
|
31
|
+
wf.register_apps()
|
|
32
|
+
wf.run()
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import git
|
|
2
|
+
import os
|
|
3
|
+
from urllib.parse import urljoin
|
|
4
|
+
import docker
|
|
5
|
+
from docker import DockerClient
|
|
6
|
+
import sys
|
|
7
|
+
from git import GitError
|
|
8
|
+
|
|
9
|
+
from FeatureCloud.api.imp.exceptions import FCException
|
|
10
|
+
from FeatureCloud.api.imp.util import getcwd_fslash, get_docker_client, remove_dir
|
|
11
|
+
import pydot
|
|
12
|
+
import importlib
|
|
13
|
+
|
|
14
|
+
FC_REPO_PREFIX = "featurecloud.ai/"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_link(template_name: str) -> str:
|
|
18
|
+
TEMPLATE_URL = 'https://github.com/FeatureCloud/'
|
|
19
|
+
return urljoin(TEMPLATE_URL, template_name + '.git')
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def new(name: str, directory: str = '.', template_name: str = 'app-blank') -> str:
|
|
23
|
+
"""
|
|
24
|
+
Parameters
|
|
25
|
+
----------
|
|
26
|
+
name: str
|
|
27
|
+
app name
|
|
28
|
+
directory: str
|
|
29
|
+
path to create the app's directory in
|
|
30
|
+
template_name: str
|
|
31
|
+
a template repository in FeatureCloud repositories
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
str
|
|
35
|
+
path to the created app
|
|
36
|
+
Raises
|
|
37
|
+
-------
|
|
38
|
+
FeatureCloud.api.imp.exceptions.FCException
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
if not template_name.startswith('app-'):
|
|
42
|
+
raise FCException(f'{template_name} is not a valid template. Please see the help for available templates')
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
app_path = os.path.join(directory, name.lower())
|
|
46
|
+
# depth=1 clones with minimal history information, we delete the .git folder anyway
|
|
47
|
+
repo = git.Repo.clone_from(create_link(template_name), app_path, multi_options=['--depth=1'])
|
|
48
|
+
repo.delete_remote('origin')
|
|
49
|
+
except GitError as e:
|
|
50
|
+
raise FCException(e)
|
|
51
|
+
|
|
52
|
+
# delete the .git folder
|
|
53
|
+
try:
|
|
54
|
+
remove_dir(os.path.join(app_path, '.git'))
|
|
55
|
+
except:
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
return app_path
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def build(path: str = ".", image_name: str = None, tag: str = "latest", rm: str = True):
|
|
62
|
+
""" Build app image.
|
|
63
|
+
|
|
64
|
+
Parameters
|
|
65
|
+
----------
|
|
66
|
+
path: str
|
|
67
|
+
path to the directory containing the Dockerfile
|
|
68
|
+
image_name: str
|
|
69
|
+
name of the app's docker image. If not provided, the name of the current directory will be used.
|
|
70
|
+
tag: str
|
|
71
|
+
versioning tag
|
|
72
|
+
rm: bool
|
|
73
|
+
if True, remove intermediate containers
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
lines
|
|
77
|
+
generator providing information about the build progress
|
|
78
|
+
Raises
|
|
79
|
+
-------
|
|
80
|
+
FeatureCloud.api.imp.exceptions.FCException
|
|
81
|
+
"""
|
|
82
|
+
if image_name is None:
|
|
83
|
+
image_name = getcwd_fslash().split("/")[-1]
|
|
84
|
+
|
|
85
|
+
client = get_docker_client()
|
|
86
|
+
try:
|
|
87
|
+
# Docker would also check for Dockerfile, but if, by accident, a path is provided with many sub folders
|
|
88
|
+
# it just takes too long. So we check it upfront:
|
|
89
|
+
if not os.path.exists(os.path.join(path, 'Dockerfile')):
|
|
90
|
+
raise FCException(f'Dockerfile not found in directory: {os.path.abspath(path)}')
|
|
91
|
+
build_log_generator = client.images.build(path=path, tag=f"{image_name.lower()}:{tag}", rm=rm)
|
|
92
|
+
if type(build_log_generator) == tuple:
|
|
93
|
+
return build_log_generator[1]
|
|
94
|
+
else:
|
|
95
|
+
raise Exception("Unexpected response from the docker SDK")
|
|
96
|
+
except docker.errors.BuildError as e:
|
|
97
|
+
raise FCException("Error in building the image: " + str(e))
|
|
98
|
+
except docker.errors.APIError as e:
|
|
99
|
+
raise FCException("Error with the API: " + str(e))
|
|
100
|
+
except TypeError as e:
|
|
101
|
+
raise FCException("Error path to the Dockerfile not given: " + str(e))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def download(name: str, tag: str = "latest"):
|
|
105
|
+
""" Download a given docker image from FeatureCloud.ai docker repo.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
name: str
|
|
110
|
+
image name
|
|
111
|
+
tag: str
|
|
112
|
+
versioning tag
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
lines
|
|
116
|
+
generator providing information about the download progress
|
|
117
|
+
Raises
|
|
118
|
+
-------
|
|
119
|
+
FeatureCloud.api.imp.exceptions.FCException
|
|
120
|
+
"""
|
|
121
|
+
fc_name = fc_repo_name(name)
|
|
122
|
+
client = get_docker_client()
|
|
123
|
+
try:
|
|
124
|
+
for entry in client.api.pull(repository=fc_name, tag=tag, stream=True, decode=True):
|
|
125
|
+
if 'error' in entry:
|
|
126
|
+
raise FCException(entry['error'])
|
|
127
|
+
|
|
128
|
+
yield entry
|
|
129
|
+
except docker.errors.DockerException as e:
|
|
130
|
+
raise FCException(e)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def publish(name: str, tag: str = "latest"):
|
|
134
|
+
""" Push a given app into FeatureCloud.ai docker repo.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
name: str
|
|
139
|
+
image name
|
|
140
|
+
tag: str
|
|
141
|
+
versioning tag
|
|
142
|
+
Returns
|
|
143
|
+
-------
|
|
144
|
+
lines
|
|
145
|
+
generator providing information about the publish progress
|
|
146
|
+
Raises
|
|
147
|
+
-------
|
|
148
|
+
FeatureCloud.api.imp.exceptions.FCException
|
|
149
|
+
"""
|
|
150
|
+
fc_name = fc_repo_name(name)
|
|
151
|
+
client = get_docker_client()
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
local_repo = f'{name}:{tag}'
|
|
155
|
+
registry_repo = f'{fc_name}:{tag}'
|
|
156
|
+
client.images.get(local_repo).tag(registry_repo)
|
|
157
|
+
except docker.errors.DockerException as e:
|
|
158
|
+
raise FCException(e)
|
|
159
|
+
|
|
160
|
+
try:
|
|
161
|
+
for entry in client.api.push(repository=registry_repo, stream=True, decode=True):
|
|
162
|
+
# Examples of stream entries:
|
|
163
|
+
# {'status': 'Waiting', 'progressDetail': {}, 'id': '38feb0548c7a'}
|
|
164
|
+
# {'errorDetail': {'message': 'unauthorized: authentication required'}, 'error': 'unauthorized: authentication required'}
|
|
165
|
+
if 'error' in entry:
|
|
166
|
+
raise FCException(entry['error'])
|
|
167
|
+
|
|
168
|
+
yield entry
|
|
169
|
+
except docker.errors.DockerException as e:
|
|
170
|
+
raise FCException(e)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def remove(name: str, tag: str = "latest"):
|
|
174
|
+
""" Delete docker image from local hard drive.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
name: str
|
|
179
|
+
image name
|
|
180
|
+
tag: str
|
|
181
|
+
the versioning tag to be removed. If set to 'all', all versions will be deleted.
|
|
182
|
+
Raises
|
|
183
|
+
-------
|
|
184
|
+
FeatureCloud.api.imp.exceptions.FCException
|
|
185
|
+
"""
|
|
186
|
+
to_find = name if tag == 'all' else f'{name}:{tag}'
|
|
187
|
+
client = get_docker_client()
|
|
188
|
+
removed = remove_images(client, to_find)
|
|
189
|
+
|
|
190
|
+
if tag == 'all':
|
|
191
|
+
# search also for local images containing the FC repository name
|
|
192
|
+
to_find = fc_repo_name(name)
|
|
193
|
+
removed = removed + remove_images(client, to_find)
|
|
194
|
+
|
|
195
|
+
return removed
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def fc_repo_name(name: str) -> str:
|
|
199
|
+
if not name.startswith(FC_REPO_PREFIX):
|
|
200
|
+
return f'{FC_REPO_PREFIX}{name}'
|
|
201
|
+
|
|
202
|
+
return name
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def remove_images(client: DockerClient, to_find: str):
|
|
206
|
+
removed = []
|
|
207
|
+
for img in client.images.list(to_find):
|
|
208
|
+
# one image id may have several tags
|
|
209
|
+
for img_tag in img.tags:
|
|
210
|
+
try:
|
|
211
|
+
client.images.remove(image=img_tag, force=True)
|
|
212
|
+
except docker.errors.DockerException as e:
|
|
213
|
+
raise FCException(e)
|
|
214
|
+
removed = removed + img.tags
|
|
215
|
+
|
|
216
|
+
return removed
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def plot_state_diagram(path: str, package: str, states: str, plot_name: str):
|
|
220
|
+
states = list(filter(None, states.split(',')))
|
|
221
|
+
sys.path.append(path)
|
|
222
|
+
if "/" in package:
|
|
223
|
+
sub_pkg_path = path
|
|
224
|
+
for sub_pkg in package.split("/"):
|
|
225
|
+
sub_pkg_path += "/" + sub_pkg
|
|
226
|
+
sys.path.append(sub_pkg_path)
|
|
227
|
+
else:
|
|
228
|
+
sys.path.append(f"{path}/{package}")
|
|
229
|
+
if states == 'main':
|
|
230
|
+
from FeatureCloud.app.engine.app import app
|
|
231
|
+
main_py = load_module(states[0], f"{path}/{package}/{states[0]}.py")
|
|
232
|
+
app = main_py.app
|
|
233
|
+
|
|
234
|
+
else:
|
|
235
|
+
for state in states:
|
|
236
|
+
load_module(state, f"{path}/{package}/{state}.py")
|
|
237
|
+
from FeatureCloud.app.engine.app import app
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
app.register()
|
|
241
|
+
|
|
242
|
+
graph = pydot.Dot('FeatureCloud State Diagram', graph_type='digraph', bgcolor='transparent')
|
|
243
|
+
|
|
244
|
+
for s in app.states:
|
|
245
|
+
state = app.states[s]
|
|
246
|
+
state_node = pydot.Node(state.name, label=state.name)
|
|
247
|
+
if state.coordinator and state.participant:
|
|
248
|
+
state_node.set('color', 'purple')
|
|
249
|
+
elif state.coordinator:
|
|
250
|
+
state_node.set('color', 'red')
|
|
251
|
+
elif state.participant:
|
|
252
|
+
state_node.set('color', 'blue')
|
|
253
|
+
if state.name == 'initial' or state.name == 'terminal':
|
|
254
|
+
state_node.set('peripheries', 2)
|
|
255
|
+
graph.add_node(state_node)
|
|
256
|
+
|
|
257
|
+
for t in app.transitions:
|
|
258
|
+
transition = app.transitions[t]
|
|
259
|
+
label = t if transition[4] is None else transition[4]
|
|
260
|
+
|
|
261
|
+
state_edge = pydot.Edge(transition[0].name, transition[1].name, label=label)
|
|
262
|
+
if transition[2] and transition[3]:
|
|
263
|
+
state_edge.set('color', 'purple')
|
|
264
|
+
elif transition[3]:
|
|
265
|
+
state_edge.set('color', 'red')
|
|
266
|
+
elif transition[2]:
|
|
267
|
+
state_edge.set('color', 'blue')
|
|
268
|
+
graph.add_edge(state_edge)
|
|
269
|
+
|
|
270
|
+
graph.write_png(f"{path}/{plot_name}.png")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def load_module(module, path):
|
|
274
|
+
loader = importlib.machinery.SourceFileLoader(module, path)
|
|
275
|
+
spec = importlib.util.spec_from_loader(module, loader)
|
|
276
|
+
mymodule = importlib.util.module_from_spec(spec)
|
|
277
|
+
loader.exec_module(mymodule)
|
|
278
|
+
return mymodule
|
|
File without changes
|