poetry-plugin-ivcap 0.3.1__tar.gz → 0.4.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.3.1 → poetry_plugin_ivcap-0.4.0}/PKG-INFO +1 -1
- {poetry_plugin_ivcap-0.3.1 → poetry_plugin_ivcap-0.4.0}/poetry_plugin_ivcap/ivcap.py +127 -0
- {poetry_plugin_ivcap-0.3.1 → poetry_plugin_ivcap-0.4.0}/poetry_plugin_ivcap/plugin.py +9 -3
- {poetry_plugin_ivcap-0.3.1 → poetry_plugin_ivcap-0.4.0}/pyproject.toml +1 -1
- {poetry_plugin_ivcap-0.3.1 → poetry_plugin_ivcap-0.4.0}/AUTHORS.md +0 -0
- {poetry_plugin_ivcap-0.3.1 → poetry_plugin_ivcap-0.4.0}/LICENSE +0 -0
- {poetry_plugin_ivcap-0.3.1 → poetry_plugin_ivcap-0.4.0}/README.md +0 -0
- {poetry_plugin_ivcap-0.3.1 → poetry_plugin_ivcap-0.4.0}/poetry_plugin_ivcap/constants.py +0 -0
- {poetry_plugin_ivcap-0.3.1 → poetry_plugin_ivcap-0.4.0}/poetry_plugin_ivcap/docker.py +0 -0
- {poetry_plugin_ivcap-0.3.1 → poetry_plugin_ivcap-0.4.0}/poetry_plugin_ivcap/util.py +0 -0
|
@@ -10,6 +10,8 @@ import sys
|
|
|
10
10
|
import tempfile
|
|
11
11
|
import uuid
|
|
12
12
|
import humanize
|
|
13
|
+
import subprocess
|
|
14
|
+
import requests
|
|
13
15
|
|
|
14
16
|
from .constants import DEF_POLICY, PLUGIN_NAME, POLICY_OPT, SERVICE_FILE_OPT, SERVICE_ID_OPT
|
|
15
17
|
|
|
@@ -129,6 +131,131 @@ def get_policy(data, line):
|
|
|
129
131
|
policy = DEF_POLICY
|
|
130
132
|
return policy
|
|
131
133
|
|
|
134
|
+
def exec_job(data, args, is_silent, line):
|
|
135
|
+
"""
|
|
136
|
+
Execute a job by posting a JSON request to the IVCAP API.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
requests.Response: The response object from the API call.
|
|
140
|
+
"""
|
|
141
|
+
# Parse 'args' for run options
|
|
142
|
+
if not isinstance(args, list) or len(args) < 1:
|
|
143
|
+
raise Exception("args must be a list with at least one element")
|
|
144
|
+
file_name = args[0]
|
|
145
|
+
timeout = 20 # default timeout
|
|
146
|
+
if len(args) == 1:
|
|
147
|
+
pass # only file_name provided
|
|
148
|
+
elif len(args) == 3 and args[1] == '--timeout':
|
|
149
|
+
try:
|
|
150
|
+
timeout = int(args[2])
|
|
151
|
+
except ValueError:
|
|
152
|
+
raise Exception("Timeout value must be an integer")
|
|
153
|
+
else:
|
|
154
|
+
raise Exception("args must be [file_name] or [file_name, '--timeout', value]")
|
|
155
|
+
|
|
156
|
+
# Get access token using ivcap CLI
|
|
157
|
+
try:
|
|
158
|
+
token = subprocess.check_output(
|
|
159
|
+
["ivcap", "--silent", "context", "get", "access-token", "--refresh-token"],
|
|
160
|
+
text=True
|
|
161
|
+
).strip()
|
|
162
|
+
except Exception as e:
|
|
163
|
+
raise RuntimeError(f"Failed to get IVCAP access token: {e}")
|
|
164
|
+
|
|
165
|
+
# Get IVCAP deployment URL
|
|
166
|
+
try:
|
|
167
|
+
ivcap_url = subprocess.check_output(
|
|
168
|
+
["ivcap", "--silent", "context", "get", "url"],
|
|
169
|
+
text=True
|
|
170
|
+
).strip()
|
|
171
|
+
except Exception as e:
|
|
172
|
+
raise RuntimeError(f"Failed to get IVCAP deployment URL: {e}")
|
|
173
|
+
|
|
174
|
+
# Read the JSON request file
|
|
175
|
+
try:
|
|
176
|
+
with open(file_name, "r", encoding="utf-8") as f:
|
|
177
|
+
json_data = f.read()
|
|
178
|
+
except Exception as e:
|
|
179
|
+
raise RuntimeError(f"Failed to read request file '{file_name}': {e}")
|
|
180
|
+
|
|
181
|
+
# Prepare headers
|
|
182
|
+
headers = {
|
|
183
|
+
"content-type": "application/json",
|
|
184
|
+
"Timeout": f"{timeout}",
|
|
185
|
+
"Authorization": f"Bearer {token}"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
# Build URL
|
|
189
|
+
service_id = get_service_id(data, is_silent, line)
|
|
190
|
+
url = f"{ivcap_url}/1/services2/{service_id}/jobs"
|
|
191
|
+
params = {"with-result-content": "true"}
|
|
192
|
+
|
|
193
|
+
# 5. POST request
|
|
194
|
+
if not is_silent:
|
|
195
|
+
line(f"<debug>Creating job '{url}'</debug>")
|
|
196
|
+
try:
|
|
197
|
+
response = requests.post(url, headers=headers, params=params, data=json_data)
|
|
198
|
+
except Exception as e:
|
|
199
|
+
raise RuntimeError(f"Job submission failed: {e}")
|
|
200
|
+
|
|
201
|
+
# Handle response according to requirements
|
|
202
|
+
import time
|
|
203
|
+
import json
|
|
204
|
+
|
|
205
|
+
def handle_response(resp):
|
|
206
|
+
content_type = resp.headers.get("content-type", "")
|
|
207
|
+
if resp.status_code >= 300:
|
|
208
|
+
line(f"<warning>WARNING: Received status code {resp.status_code}</warning>")
|
|
209
|
+
line(f"<info>Headers: {str(resp.headers)}</info>")
|
|
210
|
+
line(f"<info>Body: {str(resp.text)}</info>")
|
|
211
|
+
elif resp.status_code == 200:
|
|
212
|
+
if "application/json" in content_type:
|
|
213
|
+
try:
|
|
214
|
+
parsed = resp.json()
|
|
215
|
+
print(json.dumps(parsed, indent=2, sort_keys=True))
|
|
216
|
+
except Exception as e:
|
|
217
|
+
line(f"<warning>Failed to parse JSON response: {e}</warning>")
|
|
218
|
+
line(f"<warning>Headers: {str(resp.headers)}</warning>")
|
|
219
|
+
else:
|
|
220
|
+
line(f"<info>Headers: {str(resp.headers)}</info>")
|
|
221
|
+
else:
|
|
222
|
+
line(f"<warning>Received status code {resp.status_code}</warning>")
|
|
223
|
+
line(f"<warning>Headers: {str(resp.headers)}</warning>")
|
|
224
|
+
|
|
225
|
+
if response.status_code == 202:
|
|
226
|
+
try:
|
|
227
|
+
payload = response.json()
|
|
228
|
+
# use when ../output is fixed
|
|
229
|
+
# location = f"{payload.get('location')}/output"
|
|
230
|
+
location = f"{payload.get('location')}"
|
|
231
|
+
job_id = payload.get("job-id")
|
|
232
|
+
retry_later = payload.get("retry-later", 10)
|
|
233
|
+
if not is_silent:
|
|
234
|
+
line(f"<debug>Job '{job_id}' accepted, but no result yet. Polling in {retry_later} seconds.</debug>")
|
|
235
|
+
while True:
|
|
236
|
+
time.sleep(retry_later)
|
|
237
|
+
poll_headers = {
|
|
238
|
+
"Authorization": f"Bearer {token}"
|
|
239
|
+
}
|
|
240
|
+
poll_resp = requests.get(location, headers=poll_headers)
|
|
241
|
+
if poll_resp.status_code == 202:
|
|
242
|
+
try:
|
|
243
|
+
poll_payload = poll_resp.json()
|
|
244
|
+
location = poll_payload.get("location", location)
|
|
245
|
+
retry_later = poll_payload.get("retry-later", retry_later)
|
|
246
|
+
if not is_silent:
|
|
247
|
+
line(f"<debug>Still processing. Next poll in {retry_later} seconds.</debug>")
|
|
248
|
+
except Exception as e:
|
|
249
|
+
line(f"<error>Failed to parse polling response: {e}</error>")
|
|
250
|
+
break
|
|
251
|
+
else:
|
|
252
|
+
handle_response(poll_resp)
|
|
253
|
+
break
|
|
254
|
+
except Exception as e:
|
|
255
|
+
line(f"<error>Failed to handle 202 response: {e}</error>")
|
|
256
|
+
else:
|
|
257
|
+
handle_response(response)
|
|
258
|
+
|
|
132
259
|
def get_account_id(data, line, is_silent=False):
|
|
133
260
|
check_ivcap_cmd(line)
|
|
134
261
|
cmd = ["ivcap", "context", "get", "account-id"]
|
|
@@ -10,10 +10,11 @@ from cleo.helpers import argument, option
|
|
|
10
10
|
import subprocess
|
|
11
11
|
from importlib.metadata import version
|
|
12
12
|
|
|
13
|
-
from poetry_plugin_ivcap.constants import DOCKER_BUILD_TEMPLATE_OPT, DOCKER_RUN_TEMPLATE_OPT, PLUGIN_CMD, PLUGIN_NAME
|
|
13
|
+
from poetry_plugin_ivcap.constants import DOCKER_BUILD_TEMPLATE_OPT, DOCKER_RUN_TEMPLATE_OPT, PLUGIN_CMD, PLUGIN_NAME
|
|
14
|
+
from poetry_plugin_ivcap.constants import PORT_OPT, SERVICE_FILE_OPT, SERVICE_ID_OPT, SERVICE_TYPE_OPT, POLICY_OPT
|
|
14
15
|
from poetry_plugin_ivcap.util import get_version
|
|
15
16
|
|
|
16
|
-
from .ivcap import create_service_id, get_service_id, service_register, tool_register
|
|
17
|
+
from .ivcap import create_service_id, exec_job, get_service_id, service_register, tool_register
|
|
17
18
|
from .docker import docker_build, docker_run
|
|
18
19
|
from .ivcap import docker_publish
|
|
19
20
|
|
|
@@ -31,6 +32,7 @@ Available subcommands:
|
|
|
31
32
|
docker-build Build the docker image for this service
|
|
32
33
|
docker-run Run the service's docker image locally for testing
|
|
33
34
|
deploy Deploy the service to IVCAP (calls docker-publish, service-register and tool-register)
|
|
35
|
+
job-exec file_name Execute a job defined in 'file_name'
|
|
34
36
|
docker-publish Publish the service's docker image to IVCAP
|
|
35
37
|
service-register Register the service with IVCAP
|
|
36
38
|
create-service-id Create a unique service ID for the service
|
|
@@ -40,15 +42,17 @@ Available subcommands:
|
|
|
40
42
|
|
|
41
43
|
Example:
|
|
42
44
|
poetry {PLUGIN_CMD} run -- --port 8080
|
|
45
|
+
poetry {PLUGIN_CMD} job-exec request.json -- --timeout 0 # don't wait for result
|
|
43
46
|
|
|
44
47
|
Configurable options in pyproject.toml:
|
|
45
48
|
|
|
46
49
|
[tool.{PLUGIN_NAME}]
|
|
47
50
|
{SERVICE_FILE_OPT} = "service.py" # The Python file that implements the service
|
|
48
|
-
{SERVICE_ID_OPT} = "urn:ivcap:service:ac158a1f-dfb4-5dac-bf2e-
|
|
51
|
+
{SERVICE_ID_OPT} = "urn:ivcap:service:ac158a1f-dfb4-5dac-bf2e-00000000000" # A unique identifier for the service
|
|
49
52
|
{SERVICE_TYPE_OPT} = "lambda
|
|
50
53
|
|
|
51
54
|
# Optional
|
|
55
|
+
{POLICY_OPT} = "urn:ivcap:policy:ivcap.open.metadata"
|
|
52
56
|
{DOCKER_BUILD_TEMPLATE_OPT} = "docker buildx build -t #DOCKER_NAME# ."
|
|
53
57
|
{DOCKER_RUN_TEMPLATE_OPT} = "docker run --rm -p #PORT#:#PORT# #DOCKER_NAME#"
|
|
54
58
|
"""
|
|
@@ -88,6 +92,8 @@ Configurable options in pyproject.toml:
|
|
|
88
92
|
docker_publish(data, self.line)
|
|
89
93
|
service_register(data, self.line)
|
|
90
94
|
tool_register(data, self.line)
|
|
95
|
+
elif sub == "exec-job" or sub == "job-exec":
|
|
96
|
+
exec_job(data, args, is_silent, self.line)
|
|
91
97
|
elif sub == "docker-publish":
|
|
92
98
|
docker_publish(data, self.line)
|
|
93
99
|
elif sub == "service-register":
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|