poetry-plugin-ivcap 0.4.2__tar.gz → 0.5.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.
- {poetry_plugin_ivcap-0.4.2 → poetry_plugin_ivcap-0.5.0}/PKG-INFO +1 -1
- {poetry_plugin_ivcap-0.4.2 → poetry_plugin_ivcap-0.5.0}/poetry_plugin_ivcap/ivcap.py +113 -65
- {poetry_plugin_ivcap-0.4.2 → poetry_plugin_ivcap-0.5.0}/pyproject.toml +1 -1
- {poetry_plugin_ivcap-0.4.2 → poetry_plugin_ivcap-0.5.0}/AUTHORS.md +0 -0
- {poetry_plugin_ivcap-0.4.2 → poetry_plugin_ivcap-0.5.0}/LICENSE +0 -0
- {poetry_plugin_ivcap-0.4.2 → poetry_plugin_ivcap-0.5.0}/README.md +0 -0
- {poetry_plugin_ivcap-0.4.2 → poetry_plugin_ivcap-0.5.0}/poetry_plugin_ivcap/constants.py +0 -0
- {poetry_plugin_ivcap-0.4.2 → poetry_plugin_ivcap-0.5.0}/poetry_plugin_ivcap/docker.py +0 -0
- {poetry_plugin_ivcap-0.4.2 → poetry_plugin_ivcap-0.5.0}/poetry_plugin_ivcap/plugin.py +0 -0
- {poetry_plugin_ivcap-0.4.2 → poetry_plugin_ivcap-0.5.0}/poetry_plugin_ivcap/util.py +0 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
# Use of this source code is governed by a BSD-style license that can be
|
|
4
4
|
# found in the LICENSE file. See the AUTHORS file for names of contributors.
|
|
5
5
|
#
|
|
6
|
+
import argparse
|
|
6
7
|
import os
|
|
7
8
|
import re
|
|
8
9
|
import subprocess
|
|
@@ -12,6 +13,9 @@ import uuid
|
|
|
12
13
|
import humanize
|
|
13
14
|
import subprocess
|
|
14
15
|
import requests
|
|
16
|
+
import time
|
|
17
|
+
import json
|
|
18
|
+
|
|
15
19
|
|
|
16
20
|
from .constants import DEF_POLICY, PLUGIN_NAME, POLICY_OPT, SERVICE_FILE_OPT, SERVICE_ID_OPT, DEF_IVCAP_BASE_URL
|
|
17
21
|
|
|
@@ -143,20 +147,16 @@ def exec_job(data, args, is_silent, line):
|
|
|
143
147
|
requests.Response: The response object from the API call.
|
|
144
148
|
"""
|
|
145
149
|
# Parse 'args' for run options
|
|
150
|
+
p = exec_parser()
|
|
146
151
|
if not isinstance(args, list) or len(args) < 1:
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
except ValueError:
|
|
156
|
-
raise Exception("Timeout value must be an integer")
|
|
157
|
-
else:
|
|
158
|
-
raise Exception("args must be [file_name] or [file_name, '--timeout', value]")
|
|
159
|
-
|
|
152
|
+
p.error("missing request file name")
|
|
153
|
+
# raise Exception("args must be a list with at least one element")
|
|
154
|
+
file_name = args.pop(0)
|
|
155
|
+
if file_name == "-h" or file_name == "--help":
|
|
156
|
+
p.print_help()
|
|
157
|
+
return
|
|
158
|
+
pa = p.parse_args(args)
|
|
159
|
+
timeout = 0 if pa.stream else pa.timeout
|
|
160
160
|
# Get access token using ivcap CLI
|
|
161
161
|
try:
|
|
162
162
|
token = subprocess.check_output(
|
|
@@ -202,34 +202,6 @@ def exec_job(data, args, is_silent, line):
|
|
|
202
202
|
except Exception as e:
|
|
203
203
|
raise RuntimeError(f"Job submission failed: {e}")
|
|
204
204
|
|
|
205
|
-
# Handle response according to requirements
|
|
206
|
-
import time
|
|
207
|
-
import json
|
|
208
|
-
|
|
209
|
-
def handle_response(resp):
|
|
210
|
-
content_type = resp.headers.get("content-type", "")
|
|
211
|
-
if resp.status_code >= 300:
|
|
212
|
-
line(f"<warning>WARNING: Received status code {resp.status_code}</warning>")
|
|
213
|
-
line(f"<info>Headers: {str(resp.headers)}</info>")
|
|
214
|
-
line(f"<info>Body: {str(resp.text)}</info>")
|
|
215
|
-
elif resp.status_code == 200:
|
|
216
|
-
if "application/json" in content_type:
|
|
217
|
-
try:
|
|
218
|
-
parsed = resp.json()
|
|
219
|
-
status = parsed.get("status")
|
|
220
|
-
if status and (not status in ["succeeded", "failed", "error"]):
|
|
221
|
-
return status
|
|
222
|
-
print(json.dumps(parsed, indent=2, sort_keys=True))
|
|
223
|
-
return None
|
|
224
|
-
except Exception as e:
|
|
225
|
-
line(f"<warning>Failed to parse JSON response: {e}</warning>")
|
|
226
|
-
line(f"<warning>Headers: {str(resp.headers)}</warning>")
|
|
227
|
-
else:
|
|
228
|
-
line(f"<info>Headers: {str(resp.headers)}</info>")
|
|
229
|
-
else:
|
|
230
|
-
line(f"<warning>Received status code {resp.status_code}</warning>")
|
|
231
|
-
line(f"<warning>Headers: {str(resp.headers)}</warning>")
|
|
232
|
-
return "unknown"
|
|
233
205
|
|
|
234
206
|
if response.status_code == 202:
|
|
235
207
|
try:
|
|
@@ -241,34 +213,110 @@ def exec_job(data, args, is_silent, line):
|
|
|
241
213
|
retry_later = payload.get("retry-later", 5)
|
|
242
214
|
if not is_silent:
|
|
243
215
|
line(f"<debug>Job '{job_id}' accepted, but no result yet. Polling in {retry_later} seconds.</debug>")
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
poll_resp = requests.get(location, headers=poll_headers)
|
|
250
|
-
if poll_resp.status_code == 202:
|
|
251
|
-
try:
|
|
252
|
-
poll_payload = poll_resp.json()
|
|
253
|
-
location = poll_payload.get("location", location)
|
|
254
|
-
retry_later = poll_payload.get("retry-later", retry_later)
|
|
255
|
-
if not is_silent:
|
|
256
|
-
line(f"<debug>Still processing. Next poll in {retry_later} seconds.</debug>")
|
|
257
|
-
except Exception as e:
|
|
258
|
-
line(f"<error>Failed to parse polling response: {e}</error>")
|
|
259
|
-
break
|
|
260
|
-
else:
|
|
261
|
-
status = handle_response(poll_resp)
|
|
262
|
-
if status:
|
|
263
|
-
if not is_silent:
|
|
264
|
-
line(f"<debug>Status: '{status}'. Next poll in {retry_later} seconds.</debug>")
|
|
265
|
-
else:
|
|
266
|
-
break
|
|
216
|
+
if pa.stream:
|
|
217
|
+
stream_result(location, token, pa)
|
|
218
|
+
else:
|
|
219
|
+
poll_for_result(location, retry_later, token, is_silent, line)
|
|
267
220
|
|
|
268
221
|
except Exception as e:
|
|
269
222
|
line(f"<error>Failed to handle 202 response: {e}</error>")
|
|
270
223
|
else:
|
|
271
|
-
handle_response(response)
|
|
224
|
+
handle_response(response, line)
|
|
225
|
+
|
|
226
|
+
def handle_response(resp, line):
|
|
227
|
+
content_type = resp.headers.get("content-type", "")
|
|
228
|
+
if resp.status_code >= 300:
|
|
229
|
+
line(f"<warning>WARNING: Received status code {resp.status_code}</warning>")
|
|
230
|
+
line(f"<info>Headers: {str(resp.headers)}</info>")
|
|
231
|
+
line(f"<info>Body: {str(resp.text)}</info>")
|
|
232
|
+
elif resp.status_code == 200:
|
|
233
|
+
if "application/json" in content_type:
|
|
234
|
+
try:
|
|
235
|
+
parsed = resp.json()
|
|
236
|
+
status = parsed.get("status")
|
|
237
|
+
if status and (not status in ["succeeded", "failed", "error"]):
|
|
238
|
+
return status
|
|
239
|
+
print(json.dumps(parsed, indent=2, sort_keys=True))
|
|
240
|
+
return None
|
|
241
|
+
except Exception as e:
|
|
242
|
+
line(f"<warning>Failed to parse JSON response: {e}</warning>")
|
|
243
|
+
line(f"<warning>Headers: {str(resp.headers)}</warning>")
|
|
244
|
+
else:
|
|
245
|
+
line(f"<info>Headers: {str(resp.headers)}</info>")
|
|
246
|
+
else:
|
|
247
|
+
line(f"<warning>Received status code {resp.status_code}</warning>")
|
|
248
|
+
line(f"<warning>Headers: {str(resp.headers)}</warning>")
|
|
249
|
+
return "unknown"
|
|
250
|
+
|
|
251
|
+
def poll_for_result(location, retry_later, token, is_silent, line):
|
|
252
|
+
while True:
|
|
253
|
+
time.sleep(retry_later)
|
|
254
|
+
poll_headers = {
|
|
255
|
+
"Authorization": f"Bearer {token}"
|
|
256
|
+
}
|
|
257
|
+
poll_resp = requests.get(location, headers=poll_headers)
|
|
258
|
+
if poll_resp.status_code == 202:
|
|
259
|
+
try:
|
|
260
|
+
poll_payload = poll_resp.json()
|
|
261
|
+
location = poll_payload.get("location", location)
|
|
262
|
+
retry_later = poll_payload.get("retry-later", retry_later)
|
|
263
|
+
if not is_silent:
|
|
264
|
+
line(f"<debug>Still processing. Next poll in {retry_later} seconds.</debug>")
|
|
265
|
+
except Exception as e:
|
|
266
|
+
line(f"<error>Failed to parse polling response: {e}</error>")
|
|
267
|
+
break
|
|
268
|
+
else:
|
|
269
|
+
status = handle_response(poll_resp, line)
|
|
270
|
+
if status:
|
|
271
|
+
if not is_silent:
|
|
272
|
+
line(f"<debug>Status: '{status}'. Next poll in {retry_later} seconds.</debug>")
|
|
273
|
+
else:
|
|
274
|
+
break
|
|
275
|
+
|
|
276
|
+
def stream_result(location, token, pa):
|
|
277
|
+
"""
|
|
278
|
+
Stream the result content from the given location using the provided token.
|
|
279
|
+
"""
|
|
280
|
+
headers = {
|
|
281
|
+
"Authorization": f"Bearer {token}",
|
|
282
|
+
"Accept": "text/event-stream"
|
|
283
|
+
}
|
|
284
|
+
with requests.get(location + "/events", stream=True, headers=headers, timeout=(5, 65)) as r:
|
|
285
|
+
r.raise_for_status()
|
|
286
|
+
for row in r.iter_lines(decode_unicode=True, chunk_size=1):
|
|
287
|
+
if row is None:
|
|
288
|
+
continue
|
|
289
|
+
if row.startswith(":"):
|
|
290
|
+
# comment/heartbeat
|
|
291
|
+
continue
|
|
292
|
+
print_sse_row(row, pa) # raw SSE lines (e.g., "data: {...}", "event: message")
|
|
293
|
+
|
|
294
|
+
def print_sse_row(row, pa):
|
|
295
|
+
if pa.raw_events:
|
|
296
|
+
print(row)
|
|
297
|
+
elif row.startswith("data: "):
|
|
298
|
+
# JSON data
|
|
299
|
+
print("----")
|
|
300
|
+
try:
|
|
301
|
+
data = json.loads(row[6:])
|
|
302
|
+
print(json.dumps(data, indent=2, sort_keys=True))
|
|
303
|
+
except json.JSONDecodeError as e:
|
|
304
|
+
print(f"Failed to decode JSON: {e}")
|
|
305
|
+
|
|
306
|
+
def exec_parser():
|
|
307
|
+
p = argparse.ArgumentParser(prog="poetry ivcap job-exec request_file --")
|
|
308
|
+
p.add_argument("--timeout", type=nonneg_int, default=5, help="[5] seconds; 0 allowed")
|
|
309
|
+
p.add_argument("--with-result-content", dest="with_result_content", action="store_true",
|
|
310
|
+
help="include result content in the response")
|
|
311
|
+
p.add_argument("--stream", action="store_true", help="stream the result content")
|
|
312
|
+
p.add_argument("--raw-events", action="store_true", help="print raw SSE events")
|
|
313
|
+
return p
|
|
314
|
+
|
|
315
|
+
def nonneg_int(s: str) -> int:
|
|
316
|
+
v = int(s)
|
|
317
|
+
if v < 0:
|
|
318
|
+
raise argparse.ArgumentTypeError("timeout must be >= 0")
|
|
319
|
+
return v
|
|
272
320
|
|
|
273
321
|
def get_account_id(data, line, is_silent=False):
|
|
274
322
|
check_ivcap_cmd(line)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|