accli 2.0.14__tar.gz → 2.1.0__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.
- {accli-2.0.14 → accli-2.1.0}/PKG-INFO +26 -9
- {accli-2.0.14 → accli-2.1.0}/README.md +25 -8
- accli-2.1.0/accli/AcceleratorJobProjectService.py +397 -0
- {accli-2.0.14 → accli-2.1.0}/accli/AcceleratorTaskDispatcher.py +17 -3
- accli-2.1.0/accli/AcceleratorTerminalCliProjectService.py +220 -0
- {accli-2.0.14 → accli-2.1.0}/accli/_version.py +1 -1
- {accli-2.0.14 → accli-2.1.0}/accli/cli.py +238 -8
- {accli-2.0.14 → accli-2.1.0}/accli/gui.py +19 -0
- accli-2.1.0/accli/job_views.py +233 -0
- accli-2.1.0/accli/log_timeline.py +121 -0
- accli-2.1.0/accli/version_checker.py +55 -0
- {accli-2.0.14 → accli-2.1.0}/accli.egg-info/PKG-INFO +26 -9
- {accli-2.0.14 → accli-2.1.0}/accli.egg-info/SOURCES.txt +6 -0
- accli-2.1.0/tests/test_jobs_commands.py +140 -0
- accli-2.1.0/tests/test_log_timeline.py +28 -0
- accli-2.1.0/tests/test_logs_command.py +67 -0
- accli-2.0.14/accli/AcceleratorJobProjectService.py +0 -830
- accli-2.0.14/accli/AcceleratorTerminalCliProjectService.py +0 -397
- {accli-2.0.14 → accli-2.1.0}/LICENSE +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli/CsvRegionalTimeseriesValidator.py +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli/__init__.py +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli/__main__.py +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli/cleanup-nfs.ps1 +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli/common.py +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli/mount_downloader.py +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli/nfs_watcher.py +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli/refresh_explorer.ps1 +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli/setup-nfs.ps1 +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli/token.py +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli.egg-info/dependency_links.txt +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli.egg-info/entry_points.txt +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli.egg-info/requires.txt +0 -0
- {accli-2.0.14 → accli-2.1.0}/accli.egg-info/top_level.txt +0 -0
- {accli-2.0.14 → accli-2.1.0}/pyproject.toml +0 -0
- {accli-2.0.14 → accli-2.1.0}/setup.cfg +0 -0
- {accli-2.0.14 → accli-2.1.0}/tests/test_mount_lifecycle.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: accli
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: IIASA Accelerator Client
|
|
5
5
|
Author-email: Wrufesh S <wrufesh@gmail.com>
|
|
6
6
|
License: The MIT License (MIT)
|
|
@@ -132,20 +132,37 @@ accli --help
|
|
|
132
132
|
Usage: accli [OPTIONS] COMMAND [ARGS]...
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
+
### 📋 Jobs
|
|
136
|
+
|
|
137
|
+
List root jobs, newest first, with each job's type (`standalone`, `children`, `callback` or `children+callback`), its own status and the status of its whole graph:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
accli jobs # latest 10 in the last project used
|
|
141
|
+
accli jobs -p my-project -n 50
|
|
142
|
+
accli jobs -s failed # graphs with a failed job
|
|
143
|
+
accli jobs -s processing,error --all
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`--status` filters on the graph status: `processing` (or `active`), `error` (or `failed`) and `done`. A standalone job counts by its own status.
|
|
147
|
+
|
|
148
|
+
Show a job's status and details. For a job holding children or callbacks, Graph status covers the jobs below it (the same as GRAPH in `accli jobs`) and each one is listed; any job also links to the pipeline it is part of. Read a job's output with `accli logs <job>`:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
accli job 1234
|
|
152
|
+
accli job 1234 --json
|
|
153
|
+
```
|
|
154
|
+
|
|
135
155
|
---
|
|
136
156
|
|
|
137
157
|
## 👩💻 Developer Guide
|
|
138
158
|
|
|
139
159
|
### Handling self-signed certificates
|
|
140
160
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
cd scripts
|
|
147
|
-
.\import_certs_certutil.ps1
|
|
148
|
-
```
|
|
161
|
+
Import the certificates from `accelerator_service` into the Windows Root store (auto-elevates to Administrator if needed):
|
|
162
|
+
```powershell
|
|
163
|
+
cd <path to>/accelerator_service/scripts
|
|
164
|
+
.\import_certs_certutil.ps1
|
|
165
|
+
```
|
|
149
166
|
|
|
150
167
|
### 🛠 Build & Upload
|
|
151
168
|
|
|
@@ -88,20 +88,37 @@ accli --help
|
|
|
88
88
|
Usage: accli [OPTIONS] COMMAND [ARGS]...
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
### 📋 Jobs
|
|
92
|
+
|
|
93
|
+
List root jobs, newest first, with each job's type (`standalone`, `children`, `callback` or `children+callback`), its own status and the status of its whole graph:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
accli jobs # latest 10 in the last project used
|
|
97
|
+
accli jobs -p my-project -n 50
|
|
98
|
+
accli jobs -s failed # graphs with a failed job
|
|
99
|
+
accli jobs -s processing,error --all
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`--status` filters on the graph status: `processing` (or `active`), `error` (or `failed`) and `done`. A standalone job counts by its own status.
|
|
103
|
+
|
|
104
|
+
Show a job's status and details. For a job holding children or callbacks, Graph status covers the jobs below it (the same as GRAPH in `accli jobs`) and each one is listed; any job also links to the pipeline it is part of. Read a job's output with `accli logs <job>`:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
accli job 1234
|
|
108
|
+
accli job 1234 --json
|
|
109
|
+
```
|
|
110
|
+
|
|
91
111
|
---
|
|
92
112
|
|
|
93
113
|
## 👩💻 Developer Guide
|
|
94
114
|
|
|
95
115
|
### Handling self-signed certificates
|
|
96
116
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
cd scripts
|
|
103
|
-
.\import_certs_certutil.ps1
|
|
104
|
-
```
|
|
117
|
+
Import the certificates from `accelerator_service` into the Windows Root store (auto-elevates to Administrator if needed):
|
|
118
|
+
```powershell
|
|
119
|
+
cd <path to>/accelerator_service/scripts
|
|
120
|
+
.\import_certs_certutil.ps1
|
|
121
|
+
```
|
|
105
122
|
|
|
106
123
|
### 🛠 Build & Upload
|
|
107
124
|
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import io
|
|
3
|
+
from typing import Union, List
|
|
4
|
+
|
|
5
|
+
import requests
|
|
6
|
+
import json
|
|
7
|
+
import base64
|
|
8
|
+
import urllib3
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from accli.common import todict
|
|
12
|
+
|
|
13
|
+
ACCLI_DEBUG = os.environ.get('ACCLI_DEBUG', False)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AccAPIError(Exception):
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
# def __init__(self, *args, **kwargs):
|
|
20
|
+
# self.status_code = kwargs.pop('status_code')
|
|
21
|
+
# self.response_data = kwargs.pop('response_data')
|
|
22
|
+
# super().__init__(*args, **kwargs)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
retries = urllib3.util.Retry(total=10, backoff_factor=1)
|
|
26
|
+
|
|
27
|
+
http_client = urllib3.poolmanager.PoolManager(
|
|
28
|
+
num_pools=20, retries=retries, maxsize=2000, block=True
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
http_client_wo_cert_verification = urllib3.poolmanager.PoolManager(
|
|
32
|
+
cert_reqs="CERT_NONE", num_pools=20, retries=retries, maxsize=2000, block=True
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AcceleratorJobProjectService:
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
user_token,
|
|
40
|
+
server_url='http://accelerator.iiasa.ac.at',
|
|
41
|
+
verify_cert=(not ACCLI_DEBUG)
|
|
42
|
+
):
|
|
43
|
+
|
|
44
|
+
self.user_token = user_token
|
|
45
|
+
|
|
46
|
+
if verify_cert:
|
|
47
|
+
self.http_client = http_client
|
|
48
|
+
else:
|
|
49
|
+
self.http_client = http_client_wo_cert_verification
|
|
50
|
+
|
|
51
|
+
self.cli_base_url = f"{server_url}/api/v1/ajob-cli"
|
|
52
|
+
self.common_request_headers = {
|
|
53
|
+
'x-authorization': user_token
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
def http_client_request(self, *args, **kwargs):
|
|
57
|
+
|
|
58
|
+
if urllib3.__version__.startswith('1.'):
|
|
59
|
+
if 'json' in kwargs:
|
|
60
|
+
json_dict = kwargs.pop('json')
|
|
61
|
+
|
|
62
|
+
assert isinstance(json_dict, dict), (
|
|
63
|
+
f"urllib request: json keyword argument should be dict."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
encoded_data = json.dumps(json_dict).encode('utf-8')
|
|
67
|
+
|
|
68
|
+
kwargs['body'] = encoded_data
|
|
69
|
+
|
|
70
|
+
res = self.http_client.request(*args, **kwargs)
|
|
71
|
+
|
|
72
|
+
if str(res.status)[0] in ['4', '5']:
|
|
73
|
+
raise AccAPIError(
|
|
74
|
+
f"Accelerator api error:: status_code={res.status} :: response_data={res.data}",
|
|
75
|
+
)
|
|
76
|
+
return res
|
|
77
|
+
|
|
78
|
+
def get_file_stat(self, bucket_object_id):
|
|
79
|
+
res = self.http_client_request(
|
|
80
|
+
"GET",
|
|
81
|
+
f"{self.cli_base_url}/file-stat/{bucket_object_id}/",
|
|
82
|
+
headers=self.common_request_headers
|
|
83
|
+
)
|
|
84
|
+
return todict(res.data)
|
|
85
|
+
|
|
86
|
+
def enumerate_files_by_prefix(self, prefix):
|
|
87
|
+
raise NotImplementedError("filename parameter is not implemented yet")
|
|
88
|
+
|
|
89
|
+
project_slug = filename.split('/')[0]
|
|
90
|
+
|
|
91
|
+
b64_encoded_prefix = base64.b64encode(prefix.encode()).decode()
|
|
92
|
+
|
|
93
|
+
res = self.http_client_request(
|
|
94
|
+
"GET",
|
|
95
|
+
f"{self.cli_base_url}/{project_slug}/enumerate-all-files/{b64_encoded_prefix}/",
|
|
96
|
+
headers=self.common_request_headers
|
|
97
|
+
)
|
|
98
|
+
if res.data:
|
|
99
|
+
return todict(res.data)
|
|
100
|
+
|
|
101
|
+
def get_file_url_from_repo(self, filename):
|
|
102
|
+
project_slug = filename.split('/')[0]
|
|
103
|
+
res = self.http_client_request(
|
|
104
|
+
"GET",
|
|
105
|
+
f"{self.cli_base_url}/{project_slug}/get-file-download-url/?filename={filename}",
|
|
106
|
+
headers=self.common_request_headers
|
|
107
|
+
)
|
|
108
|
+
if res.data:
|
|
109
|
+
return todict(res.data)
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
def get_dataset_type(self, *args, **kwargs):
|
|
113
|
+
return self.get_bucket_object_validation_type(*args, **kwargs)
|
|
114
|
+
|
|
115
|
+
def get_bucket_object_validation_type(self, bucket_object_id):
|
|
116
|
+
res = self.http_client_request(
|
|
117
|
+
"GET",
|
|
118
|
+
f"{self.cli_base_url}/dataset-type/{bucket_object_id}/",
|
|
119
|
+
headers=self.common_request_headers
|
|
120
|
+
)
|
|
121
|
+
if res.data:
|
|
122
|
+
return todict(res.data)
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
def get_filename_dataset_type(self, filename):
|
|
126
|
+
res = self.http_client_request(
|
|
127
|
+
"GET",
|
|
128
|
+
f"{self.cli_base_url}/filename-dataset-type/?filename={filename}",
|
|
129
|
+
headers=self.common_request_headers
|
|
130
|
+
)
|
|
131
|
+
if res.data:
|
|
132
|
+
return todict(res.data)
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
def get_filename_validation_details(self, filename):
|
|
136
|
+
res = self.http_client_request(
|
|
137
|
+
"GET",
|
|
138
|
+
f"{self.cli_base_url}/filename-validation-detail/?filename={filename}",
|
|
139
|
+
headers=self.common_request_headers
|
|
140
|
+
)
|
|
141
|
+
if res.data:
|
|
142
|
+
return todict(res.data)
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
def get_bucket_object_validation_details(self, bucket_object_id):
|
|
146
|
+
res = self.http_client_request(
|
|
147
|
+
"GET",
|
|
148
|
+
f"{self.cli_base_url}/validation-detail/{bucket_object_id}/",
|
|
149
|
+
headers=self.common_request_headers
|
|
150
|
+
)
|
|
151
|
+
if res.data:
|
|
152
|
+
return todict(res.data)
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
def get_file_url(self, bucket_object_id):
|
|
156
|
+
res = self.http_client_request(
|
|
157
|
+
"GET",
|
|
158
|
+
f"{self.cli_base_url}/file-url/{bucket_object_id}/",
|
|
159
|
+
headers=self.common_request_headers
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
if res.data:
|
|
163
|
+
return todict(res.data)
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
def get_file_stream(self, bucket_object_id):
|
|
167
|
+
url = self.get_file_url(bucket_object_id)
|
|
168
|
+
if url:
|
|
169
|
+
resp = self.http_client_request("GET", url, preload_content=False)
|
|
170
|
+
return resp
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
def check_job_health(self):
|
|
174
|
+
|
|
175
|
+
res = self.http_client_request(
|
|
176
|
+
"GET",
|
|
177
|
+
f"{self.cli_base_url}/is-healthy/",
|
|
178
|
+
headers=self.common_request_headers
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
if res.data:
|
|
182
|
+
res = todict(res.data)
|
|
183
|
+
|
|
184
|
+
is_healthy = res['is_healthy']
|
|
185
|
+
return is_healthy
|
|
186
|
+
|
|
187
|
+
def add_log_file(self, data: bytes, filename):
|
|
188
|
+
|
|
189
|
+
res = self.http_client_request(
|
|
190
|
+
"GET",
|
|
191
|
+
f"{self.cli_base_url}/presigned-log-upload-url/?filename={filename}",
|
|
192
|
+
headers=self.common_request_headers
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if res.data:
|
|
196
|
+
res = todict(res.data)
|
|
197
|
+
else:
|
|
198
|
+
raise ValueError(f"Unable to get presign url in response: {res.data}")
|
|
199
|
+
|
|
200
|
+
upload_url = res['upload_url']
|
|
201
|
+
app_bucket_id = res['app_bucket_id']
|
|
202
|
+
res_filename = res['filename']
|
|
203
|
+
is_healthy = res['is_healthy']
|
|
204
|
+
|
|
205
|
+
requests.put(
|
|
206
|
+
upload_url,
|
|
207
|
+
data=data,
|
|
208
|
+
# headers=headers,
|
|
209
|
+
verify=False,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
self.http_client_request(
|
|
213
|
+
"POST",
|
|
214
|
+
f"{self.cli_base_url}/register-log-file/",
|
|
215
|
+
json=dict(
|
|
216
|
+
filename=res_filename,
|
|
217
|
+
app_bucket_id=app_bucket_id
|
|
218
|
+
),
|
|
219
|
+
headers=self.common_request_headers
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return is_healthy
|
|
223
|
+
|
|
224
|
+
# # TODO @wrufesh ensure it requires special token
|
|
225
|
+
# def register_iamc_validation(
|
|
226
|
+
# self, validated_bucket_object_id, indexdb_bucket_object_id
|
|
227
|
+
# ):
|
|
228
|
+
|
|
229
|
+
# headers = {"Content-Type": "application/json"}
|
|
230
|
+
|
|
231
|
+
# headers.update(self.common_request_headers)
|
|
232
|
+
|
|
233
|
+
# res = self.http_client_request(
|
|
234
|
+
# "PUT",
|
|
235
|
+
# f"{self.cli_base_url}/register-iamc-validation/",
|
|
236
|
+
# json=dict(
|
|
237
|
+
# validated_bucket_object_id=validated_bucket_object_id,
|
|
238
|
+
# indexdb_bucket_object_id=indexdb_bucket_object_id
|
|
239
|
+
# ),
|
|
240
|
+
# headers=headers
|
|
241
|
+
# )
|
|
242
|
+
|
|
243
|
+
def register_validation(
|
|
244
|
+
self,
|
|
245
|
+
validated_bucket_object_id: int,
|
|
246
|
+
dataset_template_id: int,
|
|
247
|
+
validated_metadata: dict,
|
|
248
|
+
validation_supporting_bucket_object_ids: List[int]
|
|
249
|
+
):
|
|
250
|
+
headers = {"Content-Type": "application/json"}
|
|
251
|
+
|
|
252
|
+
headers.update(self.common_request_headers)
|
|
253
|
+
|
|
254
|
+
res = self.http_client_request(
|
|
255
|
+
"PUT",
|
|
256
|
+
f"{self.cli_base_url}/register-validation/",
|
|
257
|
+
json=dict(
|
|
258
|
+
validated_bucket_object_id=validated_bucket_object_id,
|
|
259
|
+
dataset_template_id=dataset_template_id,
|
|
260
|
+
validated_metadata=validated_metadata,
|
|
261
|
+
validation_supporting_bucket_object_ids=validation_supporting_bucket_object_ids
|
|
262
|
+
),
|
|
263
|
+
headers=headers
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def register_validation_with_filename(
|
|
267
|
+
self,
|
|
268
|
+
validated_filename: str,
|
|
269
|
+
dataset_template_id: int,
|
|
270
|
+
validated_metadata: dict,
|
|
271
|
+
validation_supporting_filenames: List[str]
|
|
272
|
+
):
|
|
273
|
+
headers = {"Content-Type": "application/json"}
|
|
274
|
+
|
|
275
|
+
headers.update(self.common_request_headers)
|
|
276
|
+
|
|
277
|
+
res = self.http_client_request(
|
|
278
|
+
"PUT",
|
|
279
|
+
f"{self.cli_base_url}/register-validation-with-filename/",
|
|
280
|
+
json=dict(
|
|
281
|
+
validated_filename=validated_filename,
|
|
282
|
+
dataset_template_id=dataset_template_id,
|
|
283
|
+
validated_metadata=validated_metadata,
|
|
284
|
+
validation_supporting_filenames=validation_supporting_filenames
|
|
285
|
+
),
|
|
286
|
+
headers=headers
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def get_dataset_template_details(
|
|
291
|
+
self,
|
|
292
|
+
dataset_template_id
|
|
293
|
+
):
|
|
294
|
+
headers = {"Content-Type": "application/json"}
|
|
295
|
+
|
|
296
|
+
headers.update(self.common_request_headers)
|
|
297
|
+
|
|
298
|
+
res = self.http_client_request(
|
|
299
|
+
"GET",
|
|
300
|
+
f"{self.cli_base_url}/dataset-template-detail/{dataset_template_id}/",
|
|
301
|
+
headers=headers
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
return todict(res.data)
|
|
305
|
+
|
|
306
|
+
def update_job_status(self, status):
|
|
307
|
+
headers = {
|
|
308
|
+
# "Content-Type": "application/json"
|
|
309
|
+
# 'Content-Type': 'application/x-www-form-urlencoded'
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
headers.update(self.common_request_headers)
|
|
313
|
+
|
|
314
|
+
res = self.http_client_request(
|
|
315
|
+
"POST",
|
|
316
|
+
f"{self.cli_base_url}/webhook-event/",
|
|
317
|
+
json=dict(
|
|
318
|
+
type='STATUS_UPDATE',
|
|
319
|
+
data=dict(
|
|
320
|
+
new_status=status
|
|
321
|
+
)
|
|
322
|
+
),
|
|
323
|
+
headers=headers
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class Fs:
|
|
328
|
+
@staticmethod
|
|
329
|
+
def write_stream_remote(filestream, dest_filepath, user_token, server_url):
|
|
330
|
+
raise NotImplementedError(
|
|
331
|
+
"Remote uploads through accli are no longer supported. "
|
|
332
|
+
"Write job outputs to /mnt/wdrv instead; the job agent uploads them."
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
@staticmethod
|
|
336
|
+
def write_stream_local(filestream, dest_filepath):
|
|
337
|
+
with open(dest_filepath, 'wb') as file:
|
|
338
|
+
file.write(filestream.getvalue())
|
|
339
|
+
|
|
340
|
+
@staticmethod
|
|
341
|
+
def get_file_url(remote_filepath):
|
|
342
|
+
user_token = os.environ.get("ACC_JOB_TOKEN", None)
|
|
343
|
+
server_url = os.environ.get("ACC_JOB_GATEWAY_SERVER", None)
|
|
344
|
+
|
|
345
|
+
if not (user_token and server_url):
|
|
346
|
+
raise ValueError("Remote data repository credentials not found.")
|
|
347
|
+
|
|
348
|
+
accelerator_job_service = AcceleratorJobProjectService(
|
|
349
|
+
user_token,
|
|
350
|
+
server_url=server_url,
|
|
351
|
+
verify_cert=(not ACCLI_DEBUG)
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
return accelerator_job_service.get_file_url_from_repo(
|
|
355
|
+
remote_filepath
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
@staticmethod
|
|
359
|
+
def write_file(source: Union[str, io.BytesIO], dest_filepath):
|
|
360
|
+
user_token = os.environ.get("ACC_JOB_TOKEN", None)
|
|
361
|
+
server_url = os.environ.get("ACC_JOB_GATEWAY_SERVER", None)
|
|
362
|
+
|
|
363
|
+
if isinstance(source, io.BytesIO):
|
|
364
|
+
if user_token and server_url:
|
|
365
|
+
Fs.write_stream_remote(source, dest_filepath, user_token, server_url)
|
|
366
|
+
else:
|
|
367
|
+
Fs.write_stream_local(source, dest_filepath)
|
|
368
|
+
else:
|
|
369
|
+
if type(source) != str:
|
|
370
|
+
raise ValueError("Unknown source argument")
|
|
371
|
+
source_path = Path(source)
|
|
372
|
+
if not source_path.exists():
|
|
373
|
+
raise ValueError("Source path does not exist")
|
|
374
|
+
|
|
375
|
+
with open(source, 'rb') as fstream:
|
|
376
|
+
if user_token and server_url:
|
|
377
|
+
Fs.write_stream_remote(fstream, dest_filepath, user_token, server_url)
|
|
378
|
+
else:
|
|
379
|
+
Fs.write_stream_local(fstream, dest_filepath)
|
|
380
|
+
|
|
381
|
+
@staticmethod
|
|
382
|
+
def enumerate_files_by_prefix(prefix):
|
|
383
|
+
user_token = os.environ.get("ACC_JOB_TOKEN", None)
|
|
384
|
+
server_url = os.environ.get("ACC_JOB_GATEWAY_SERVER", None)
|
|
385
|
+
|
|
386
|
+
if not (user_token and server_url):
|
|
387
|
+
raise ValueError("Remote data repository credentials not found.")
|
|
388
|
+
|
|
389
|
+
accelerator_job_service = AcceleratorJobProjectService(
|
|
390
|
+
user_token,
|
|
391
|
+
server_url=server_url,
|
|
392
|
+
verify_cert=(not ACCLI_DEBUG)
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
return accelerator_job_service.enumerate_files_by_prefix(
|
|
396
|
+
prefix
|
|
397
|
+
)
|
|
@@ -102,9 +102,11 @@ def _upload_cas_file_worker(args):
|
|
|
102
102
|
if presigned_push_url:
|
|
103
103
|
print(f"Uploading {rel_path}...")
|
|
104
104
|
with open(file_path, 'rb') as f:
|
|
105
|
+
data = f.read()
|
|
105
106
|
res = requests.put(
|
|
106
107
|
presigned_push_url,
|
|
107
|
-
data=
|
|
108
|
+
data=data,
|
|
109
|
+
headers={"Content-Length": str(len(data))},
|
|
108
110
|
verify=False,
|
|
109
111
|
)
|
|
110
112
|
res.raise_for_status()
|
|
@@ -171,6 +173,16 @@ def push_folder_job(directory, include=None, exclude=None):
|
|
|
171
173
|
return f"s3accjobstore://{manifest_name}", manifest_hash
|
|
172
174
|
|
|
173
175
|
|
|
176
|
+
JOB_SECRETS_DESCRIPTION = (
|
|
177
|
+
"Env var name to secret key, e.g. {'GIT_PAT': 'GIT_PAT'}. Keys name project or personal secrets "
|
|
178
|
+
"stored under Project > Secrets in the web app; never put a secret value here, the gateway rejects it."
|
|
179
|
+
)
|
|
180
|
+
JOB_SECRET_FILES_DESCRIPTION = (
|
|
181
|
+
"File name to secret key. The value is mounted at /var/run/secrets/wkube/<file name> instead of "
|
|
182
|
+
"an env var, which keeps it out of env dumps and child processes."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
|
|
174
186
|
class JobDispatchModel(BaseModel):
|
|
175
187
|
is_holder_job: bool = True
|
|
176
188
|
build_only_task: bool = False
|
|
@@ -189,7 +201,8 @@ class JobDispatchModel(BaseModel):
|
|
|
189
201
|
# is ignored if it is a callback and child of non-free node jobs
|
|
190
202
|
required_storage_workflow: Optional[float] = None
|
|
191
203
|
|
|
192
|
-
job_secrets: Optional[
|
|
204
|
+
job_secrets: Optional[Dict[str, str]] = Field({}, description=JOB_SECRETS_DESCRIPTION)
|
|
205
|
+
job_secret_files: Optional[Dict[str, str]] = Field({}, description=JOB_SECRET_FILES_DESCRIPTION)
|
|
193
206
|
|
|
194
207
|
timeout: Optional[int] = None
|
|
195
208
|
pvc_id: Optional[str] = None
|
|
@@ -210,7 +223,8 @@ class WKubeTaskMeta(BaseModel):
|
|
|
210
223
|
# is ignored if it is a callback and child of non-free node jobs
|
|
211
224
|
required_storage_workflow: Optional[float] = None
|
|
212
225
|
|
|
213
|
-
job_secrets: Optional[
|
|
226
|
+
job_secrets: Optional[Dict[str, str]] = Field({}, description=JOB_SECRETS_DESCRIPTION)
|
|
227
|
+
job_secret_files: Optional[Dict[str, str]] = Field({}, description=JOB_SECRET_FILES_DESCRIPTION)
|
|
214
228
|
|
|
215
229
|
timeout: int
|
|
216
230
|
|