pyxecm 2.0.0__py3-none-any.whl → 2.0.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.
Potentially problematic release.
This version of pyxecm might be problematic. Click here for more details.
- pyxecm/__init__.py +2 -1
- pyxecm/avts.py +79 -33
- pyxecm/customizer/api/app.py +45 -796
- pyxecm/customizer/api/auth/__init__.py +1 -0
- pyxecm/customizer/api/{auth.py → auth/functions.py} +2 -64
- pyxecm/customizer/api/auth/router.py +78 -0
- pyxecm/customizer/api/common/__init__.py +1 -0
- pyxecm/customizer/api/common/functions.py +47 -0
- pyxecm/customizer/api/{metrics.py → common/metrics.py} +1 -1
- pyxecm/customizer/api/common/models.py +21 -0
- pyxecm/customizer/api/{payload_list.py → common/payload_list.py} +6 -1
- pyxecm/customizer/api/common/router.py +72 -0
- pyxecm/customizer/api/settings.py +25 -0
- pyxecm/customizer/api/terminal/__init__.py +1 -0
- pyxecm/customizer/api/terminal/router.py +87 -0
- pyxecm/customizer/api/v1_csai/__init__.py +1 -0
- pyxecm/customizer/api/v1_csai/router.py +87 -0
- pyxecm/customizer/api/v1_maintenance/__init__.py +1 -0
- pyxecm/customizer/api/v1_maintenance/functions.py +100 -0
- pyxecm/customizer/api/v1_maintenance/models.py +12 -0
- pyxecm/customizer/api/v1_maintenance/router.py +76 -0
- pyxecm/customizer/api/v1_otcs/__init__.py +1 -0
- pyxecm/customizer/api/v1_otcs/functions.py +61 -0
- pyxecm/customizer/api/v1_otcs/router.py +179 -0
- pyxecm/customizer/api/v1_payload/__init__.py +1 -0
- pyxecm/customizer/api/v1_payload/functions.py +179 -0
- pyxecm/customizer/api/v1_payload/models.py +51 -0
- pyxecm/customizer/api/v1_payload/router.py +499 -0
- pyxecm/customizer/browser_automation.py +568 -326
- pyxecm/customizer/customizer.py +204 -430
- pyxecm/customizer/guidewire.py +907 -43
- pyxecm/customizer/k8s.py +243 -56
- pyxecm/customizer/m365.py +104 -15
- pyxecm/customizer/payload.py +1943 -885
- pyxecm/customizer/pht.py +19 -2
- pyxecm/customizer/servicenow.py +22 -5
- pyxecm/customizer/settings.py +9 -6
- pyxecm/helper/xml.py +69 -0
- pyxecm/otac.py +1 -1
- pyxecm/otawp.py +2104 -1535
- pyxecm/otca.py +569 -0
- pyxecm/otcs.py +201 -37
- pyxecm/otds.py +35 -13
- {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/METADATA +6 -29
- pyxecm-2.0.1.dist-info/RECORD +76 -0
- {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/WHEEL +1 -1
- pyxecm-2.0.0.dist-info/RECORD +0 -54
- /pyxecm/customizer/api/{models.py → auth/models.py} +0 -0
- {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
"""Define router for v1_payload."""
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import gzip
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
from datetime import datetime, timezone
|
|
10
|
+
from http import HTTPStatus
|
|
11
|
+
from typing import Annotated, Literal
|
|
12
|
+
|
|
13
|
+
from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile
|
|
14
|
+
from fastapi.responses import FileResponse, JSONResponse, Response, StreamingResponse
|
|
15
|
+
|
|
16
|
+
from pyxecm.customizer.api.auth.functions import get_authorized_user
|
|
17
|
+
from pyxecm.customizer.api.auth.models import User
|
|
18
|
+
from pyxecm.customizer.api.common.functions import PAYLOAD_LIST, get_settings
|
|
19
|
+
from pyxecm.customizer.api.settings import CustomizerAPISettings
|
|
20
|
+
from pyxecm.customizer.api.v1_payload.functions import prepare_dependencies, tail_log
|
|
21
|
+
from pyxecm.customizer.api.v1_payload.models import PayloadListItem, PayloadListItems, UpdatedPayloadListItem
|
|
22
|
+
from pyxecm.customizer.payload import load_payload
|
|
23
|
+
|
|
24
|
+
router = APIRouter(prefix="/api/v1/payload", tags=["payload"])
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger("pyxecm.customizer.api.v1_payload")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@router.post(path="")
|
|
30
|
+
def create_payload_item(
|
|
31
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
32
|
+
settings: Annotated[CustomizerAPISettings, Depends(get_settings)],
|
|
33
|
+
upload_file: Annotated[UploadFile, File(...)],
|
|
34
|
+
name: Annotated[str, Form()] = "",
|
|
35
|
+
dependencies: Annotated[list[int] | list[str] | None, Form()] = None,
|
|
36
|
+
enabled: Annotated[bool, Form()] = True,
|
|
37
|
+
loglevel: Annotated[
|
|
38
|
+
Literal["DEBUG", "INFO", "WARNING"] | None,
|
|
39
|
+
Form(
|
|
40
|
+
description="Loglevel for the Payload processing",
|
|
41
|
+
),
|
|
42
|
+
] = "INFO",
|
|
43
|
+
) -> PayloadListItem:
|
|
44
|
+
"""Upload a new payload item.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
user (User, optional):
|
|
48
|
+
The user who is uploading the payload. Defaults to None.
|
|
49
|
+
settings (CustomizerAPISettings):
|
|
50
|
+
The settings object.
|
|
51
|
+
upload_file (UploadFile, optional):
|
|
52
|
+
The file to upload. Defaults to File(...).
|
|
53
|
+
name (str, optional):
|
|
54
|
+
The name of the payload (if not provided we will use the file name).
|
|
55
|
+
dependencies (list of integers):
|
|
56
|
+
List of other payload items this item depends on.
|
|
57
|
+
enabled (bool):
|
|
58
|
+
Flag indicating if the payload is enabled or not.
|
|
59
|
+
loglevel (str, optional):
|
|
60
|
+
The loglevel for the payload processing. Defaults to "INFO".
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
HTTPException:
|
|
64
|
+
Raised, if payload list is not initialized.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
dict:
|
|
68
|
+
The HTTP response.
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
if dependencies:
|
|
72
|
+
dependencies = prepare_dependencies(dependencies)
|
|
73
|
+
|
|
74
|
+
# Set name if not provided
|
|
75
|
+
name = name or os.path.splitext(os.path.basename(upload_file.filename))[0]
|
|
76
|
+
file_extension = os.path.splitext(upload_file.filename)[1]
|
|
77
|
+
file_name = os.path.join(settings.temp_dir, f"{name}{file_extension}")
|
|
78
|
+
|
|
79
|
+
with open(file_name, "wb") as buffer:
|
|
80
|
+
shutil.copyfileobj(upload_file.file, buffer)
|
|
81
|
+
|
|
82
|
+
if dependencies == [-1]:
|
|
83
|
+
dependencies = []
|
|
84
|
+
|
|
85
|
+
return PayloadListItem(
|
|
86
|
+
PAYLOAD_LIST.add_payload_item(
|
|
87
|
+
name=name,
|
|
88
|
+
filename=file_name,
|
|
89
|
+
status="planned",
|
|
90
|
+
logfile=os.path.join(settings.temp_dir, "{}.log".format(name)),
|
|
91
|
+
dependencies=dependencies or [],
|
|
92
|
+
enabled=enabled,
|
|
93
|
+
loglevel=loglevel,
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@router.get(path="")
|
|
99
|
+
async def get_payload_items(
|
|
100
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
101
|
+
) -> PayloadListItems:
|
|
102
|
+
"""Get all Payload items.
|
|
103
|
+
|
|
104
|
+
Raises:
|
|
105
|
+
HTTPException: payload list not initialized
|
|
106
|
+
HTTPException: payload list is empty
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
dict:
|
|
110
|
+
HTTP response with the result data
|
|
111
|
+
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
df = PAYLOAD_LIST.get_payload_items()
|
|
115
|
+
|
|
116
|
+
if df is None:
|
|
117
|
+
raise HTTPException(
|
|
118
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
119
|
+
detail="Payload list is empty.",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
data = [PayloadListItem(index=idx, **row) for idx, row in df.iterrows()]
|
|
123
|
+
|
|
124
|
+
stats = {
|
|
125
|
+
"count": len(df),
|
|
126
|
+
"status": df["status"].value_counts().to_dict(),
|
|
127
|
+
"logs": {
|
|
128
|
+
"debug": df["log_debug"].sum(),
|
|
129
|
+
"info": df["log_info"].sum(),
|
|
130
|
+
"warning": df["log_warning"].sum(),
|
|
131
|
+
"error": df["log_error"].sum(),
|
|
132
|
+
"critical": df["log_critical"].sum(),
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return PayloadListItems(stats=stats, results=data)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@router.get(path="/{payload_id}")
|
|
140
|
+
async def get_payload_item(
|
|
141
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
142
|
+
payload_id: int,
|
|
143
|
+
) -> PayloadListItem:
|
|
144
|
+
"""Get a payload item based on its ID.
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
user: Annotated[User, Depends(get_authorized_user)]
|
|
148
|
+
payload_id (int): payload item ID
|
|
149
|
+
|
|
150
|
+
Raises:
|
|
151
|
+
HTTPException: a payload item with the given ID couldn't be found
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
dict:
|
|
155
|
+
HTTP response.
|
|
156
|
+
|
|
157
|
+
"""
|
|
158
|
+
data = PAYLOAD_LIST.get_payload_item(index=payload_id)
|
|
159
|
+
|
|
160
|
+
if data is None:
|
|
161
|
+
raise HTTPException(
|
|
162
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
163
|
+
detail="Payload with index -> {} not found".format(payload_id),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
return PayloadListItem(index=payload_id, **data, asd="123")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
@router.put(path="/{payload_id}")
|
|
170
|
+
async def update_payload_item(
|
|
171
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
172
|
+
payload_id: int,
|
|
173
|
+
name: Annotated[str | None, Form()] = None,
|
|
174
|
+
dependencies: Annotated[list[int] | list[str] | None, Form()] = None,
|
|
175
|
+
enabled: Annotated[bool | None, Form()] = None,
|
|
176
|
+
status: Annotated[Literal["planned", "completed"] | None, Form()] = None,
|
|
177
|
+
loglevel: Annotated[
|
|
178
|
+
Literal["DEBUG", "INFO", "WARNING"] | None,
|
|
179
|
+
Form(
|
|
180
|
+
description="Loglevel for the Payload processing",
|
|
181
|
+
),
|
|
182
|
+
] = None,
|
|
183
|
+
customizer_settings: Annotated[str | None, Form()] = None,
|
|
184
|
+
) -> UpdatedPayloadListItem:
|
|
185
|
+
"""Update an existing payload item.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
user (Optional[User]): User performing the update.
|
|
189
|
+
payload_id (int): ID of the payload to update.
|
|
190
|
+
upload_file (UploadFile, optional): replace the file name
|
|
191
|
+
name (Optional[str]): Updated name.
|
|
192
|
+
dependencies (Optional[List[int]]): Updated list of dependencies.
|
|
193
|
+
enabled (Optional[bool]): Updated enabled status.
|
|
194
|
+
loglevel (Optional[str]): Updated loglevel.
|
|
195
|
+
status (Optional[str]): Updated status.
|
|
196
|
+
customizer_settings (Optional[str]): Updated customizer settings.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
dict: HTTP response with the updated payload details.
|
|
200
|
+
|
|
201
|
+
"""
|
|
202
|
+
|
|
203
|
+
if dependencies:
|
|
204
|
+
dependencies = prepare_dependencies(dependencies)
|
|
205
|
+
# Check if the payload exists
|
|
206
|
+
payload_item = PAYLOAD_LIST.get_payload_item(
|
|
207
|
+
payload_id,
|
|
208
|
+
) # Assumes a method to retrieve payload by ID
|
|
209
|
+
if payload_item is None:
|
|
210
|
+
raise HTTPException(
|
|
211
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
212
|
+
detail="Payload with ID -> {} not found.".format(payload_id),
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
update_data = {}
|
|
216
|
+
|
|
217
|
+
# Update fields if provided
|
|
218
|
+
if name is not None:
|
|
219
|
+
update_data["name"] = name
|
|
220
|
+
if dependencies is not None:
|
|
221
|
+
update_data["dependencies"] = dependencies
|
|
222
|
+
if enabled is not None:
|
|
223
|
+
update_data["enabled"] = enabled
|
|
224
|
+
if status is not None:
|
|
225
|
+
update_data["status"] = status
|
|
226
|
+
if loglevel is not None:
|
|
227
|
+
update_data["loglevel"] = loglevel
|
|
228
|
+
|
|
229
|
+
thread_logger = logging.getLogger(name=f"Payload_{payload_id}")
|
|
230
|
+
thread_logger.setLevel(loglevel)
|
|
231
|
+
|
|
232
|
+
if customizer_settings is not None:
|
|
233
|
+
try:
|
|
234
|
+
update_data["customizer_settings"] = json.loads(customizer_settings)
|
|
235
|
+
except Exception as e:
|
|
236
|
+
raise HTTPException(detail=e, status_code=HTTPStatus.BAD_REQUEST) from e
|
|
237
|
+
|
|
238
|
+
if "status" in update_data and update_data["status"] == "planned":
|
|
239
|
+
logger.info("Resetting log message counters for -> %s", payload_id)
|
|
240
|
+
update_data["log_debug"] = 0
|
|
241
|
+
update_data["log_info"] = 0
|
|
242
|
+
update_data["log_warning"] = 0
|
|
243
|
+
update_data["log_error"] = 0
|
|
244
|
+
update_data["log_critical"] = 0
|
|
245
|
+
|
|
246
|
+
update_data["start_time"] = None
|
|
247
|
+
update_data["stop_time"] = None
|
|
248
|
+
update_data["duration"] = None
|
|
249
|
+
|
|
250
|
+
data = PAYLOAD_LIST.get_payload_item(index=payload_id)
|
|
251
|
+
if os.path.isfile(data.logfile):
|
|
252
|
+
logger.info(
|
|
253
|
+
"Deleting log file (for payload) -> %s (%s)",
|
|
254
|
+
data.logfile,
|
|
255
|
+
payload_id,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
now = datetime.now(timezone.utc)
|
|
259
|
+
old_log_name = (
|
|
260
|
+
os.path.dirname(data.logfile)
|
|
261
|
+
+ "/"
|
|
262
|
+
+ os.path.splitext(os.path.basename(data.logfile))[0]
|
|
263
|
+
+ now.strftime("_%Y-%m-%d_%H-%M-%S.log")
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
os.rename(data.logfile, old_log_name)
|
|
267
|
+
|
|
268
|
+
# Save the updated payload back to the list (or database)
|
|
269
|
+
result = PAYLOAD_LIST.update_payload_item(
|
|
270
|
+
index=payload_id,
|
|
271
|
+
update_data=update_data,
|
|
272
|
+
) # Assumes a method to update the payload
|
|
273
|
+
if not result:
|
|
274
|
+
raise HTTPException(
|
|
275
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
276
|
+
detail="Failed to update Payload with ID -> {} with data -> {}".format(
|
|
277
|
+
payload_id,
|
|
278
|
+
update_data,
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
return UpdatedPayloadListItem(
|
|
283
|
+
message="Payload updated successfully",
|
|
284
|
+
payload=PayloadListItem(index=payload_id, **PAYLOAD_LIST.get_payload_item(index=payload_id)),
|
|
285
|
+
updated_fields=update_data,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
@router.delete(path="/{payload_id}")
|
|
290
|
+
async def delete_payload_item(
|
|
291
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
292
|
+
payload_id: int,
|
|
293
|
+
) -> JSONResponse:
|
|
294
|
+
"""Delete an existing payload item.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
user (Optional[User]): User performing the update.
|
|
298
|
+
payload_id (int): The ID of the payload to update.
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
dict: response or None
|
|
302
|
+
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
# Check if the payload exists
|
|
306
|
+
result = PAYLOAD_LIST.remove_payload_item(payload_id)
|
|
307
|
+
if not result:
|
|
308
|
+
raise HTTPException(
|
|
309
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
310
|
+
detail="Payload with ID -> {} not found.".format(payload_id),
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@router.put(path="/{payload_id}/up")
|
|
315
|
+
async def move_payload_item_up(
|
|
316
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
317
|
+
payload_id: int,
|
|
318
|
+
) -> dict:
|
|
319
|
+
"""Move a payload item up in the list.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
user: Annotated[User, Depends(get_authorized_user)]
|
|
323
|
+
payload_id (int): payload item ID
|
|
324
|
+
|
|
325
|
+
Raises:
|
|
326
|
+
HTTPException: a payload item with the given ID couldn't be found
|
|
327
|
+
|
|
328
|
+
Returns:
|
|
329
|
+
dict: HTTP response
|
|
330
|
+
|
|
331
|
+
"""
|
|
332
|
+
|
|
333
|
+
position = PAYLOAD_LIST.move_payload_item_up(index=payload_id)
|
|
334
|
+
|
|
335
|
+
if position is None:
|
|
336
|
+
raise HTTPException(
|
|
337
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
338
|
+
detail="Payload item with index -> {} is either out of range or is already on top of the payload list!".format(
|
|
339
|
+
payload_id,
|
|
340
|
+
),
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
return {"result": {"new_position": position}}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@router.put(path="/{payload_id}/down")
|
|
347
|
+
async def move_payload_item_down(
|
|
348
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
349
|
+
payload_id: int,
|
|
350
|
+
) -> dict:
|
|
351
|
+
"""Move a payload item down in the list.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
user: Annotated[User, Depends(get_authorized_user)]
|
|
355
|
+
payload_id (int):
|
|
356
|
+
The payload item ID.
|
|
357
|
+
|
|
358
|
+
Raises:
|
|
359
|
+
HTTPException: a payload item with the given ID couldn't be found
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
dict: HTTP response
|
|
363
|
+
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
position = PAYLOAD_LIST.move_payload_item_down(index=payload_id)
|
|
367
|
+
|
|
368
|
+
if position is None:
|
|
369
|
+
raise HTTPException(
|
|
370
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
371
|
+
detail="Payload item with index -> {} is either out of range or is already on bottom of the payload list!".format(
|
|
372
|
+
payload_id,
|
|
373
|
+
),
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
return {"result": {"new_position": position}}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@router.get(path="/{payload_id}/content")
|
|
380
|
+
async def get_payload_content(
|
|
381
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
382
|
+
# pylint: disable=unused-argument
|
|
383
|
+
payload_id: int,
|
|
384
|
+
) -> dict | None:
|
|
385
|
+
"""Get a payload item based on its ID.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
user: Annotated[User, Depends(get_authorized_user)]
|
|
389
|
+
payload_id (int):
|
|
390
|
+
The payload item ID.
|
|
391
|
+
|
|
392
|
+
Raises:
|
|
393
|
+
HTTPException:
|
|
394
|
+
A payload item with the given ID couldn't be found.
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
dict:
|
|
398
|
+
HTTP response.
|
|
399
|
+
|
|
400
|
+
"""
|
|
401
|
+
|
|
402
|
+
data = PAYLOAD_LIST.get_payload_item(index=payload_id)
|
|
403
|
+
|
|
404
|
+
if data is None:
|
|
405
|
+
raise HTTPException(
|
|
406
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
407
|
+
detail="Payload with ID -> {} not found!".format(payload_id),
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
filename = data.filename
|
|
411
|
+
|
|
412
|
+
return load_payload(payload_source=filename)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@router.get(path="/{payload_id}/download")
|
|
416
|
+
def download_payload_content(
|
|
417
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
418
|
+
payload_id: int,
|
|
419
|
+
) -> FileResponse:
|
|
420
|
+
"""Download the payload for a specific payload item."""
|
|
421
|
+
|
|
422
|
+
payload = PAYLOAD_LIST.get_payload_item(index=payload_id)
|
|
423
|
+
|
|
424
|
+
if payload is None:
|
|
425
|
+
raise HTTPException(
|
|
426
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
427
|
+
detail="Payload with ID -> {} not found!".format(payload_id),
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
if not os.path.isfile(payload.filename):
|
|
431
|
+
raise HTTPException(
|
|
432
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
433
|
+
detail="Payload file -> '{}' not found".format(payload.filename),
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
with open(payload.filename, encoding="UTF-8") as file:
|
|
437
|
+
content = file.read()
|
|
438
|
+
|
|
439
|
+
if payload.filename.endswith(".gz.b64"):
|
|
440
|
+
content = base64.b64decode(content)
|
|
441
|
+
content = gzip.decompress(content)
|
|
442
|
+
|
|
443
|
+
return Response(
|
|
444
|
+
content,
|
|
445
|
+
media_type="application/octet-stream",
|
|
446
|
+
headers={
|
|
447
|
+
"Content-Disposition": f'attachment; filename="{os.path.basename(payload.filename.removesuffix(".gz.b64"))}"',
|
|
448
|
+
},
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@router.get(path="/{payload_id}/log")
|
|
453
|
+
def download_payload_logfile(
|
|
454
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
455
|
+
payload_id: int,
|
|
456
|
+
) -> FileResponse:
|
|
457
|
+
"""Download the logfile for a specific payload."""
|
|
458
|
+
|
|
459
|
+
payload = PAYLOAD_LIST.get_payload_item(index=payload_id)
|
|
460
|
+
|
|
461
|
+
if payload is None:
|
|
462
|
+
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Payload not found")
|
|
463
|
+
|
|
464
|
+
filename = payload.logfile
|
|
465
|
+
|
|
466
|
+
if not os.path.isfile(filename):
|
|
467
|
+
raise HTTPException(
|
|
468
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
469
|
+
detail="Log file -> '{}' not found".format(filename),
|
|
470
|
+
)
|
|
471
|
+
with open(filename, encoding="UTF-8") as file:
|
|
472
|
+
content = file.read()
|
|
473
|
+
return Response(
|
|
474
|
+
content,
|
|
475
|
+
media_type="application/octet-stream",
|
|
476
|
+
headers={
|
|
477
|
+
"Content-Disposition": f'attachment; filename="{os.path.basename(filename)}"',
|
|
478
|
+
},
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
|
|
482
|
+
@router.get(path="/{payload_id}/log/stream")
|
|
483
|
+
async def stream_logfile(
|
|
484
|
+
user: Annotated[User, Depends(get_authorized_user)], # noqa: ARG001
|
|
485
|
+
payload_id: int,
|
|
486
|
+
) -> StreamingResponse:
|
|
487
|
+
"""Stream the logfile and follow changes."""
|
|
488
|
+
|
|
489
|
+
payload = PAYLOAD_LIST.get_payload_item(index=payload_id)
|
|
490
|
+
|
|
491
|
+
if payload is None:
|
|
492
|
+
raise HTTPException(status_code=HTTPStatus.NOT_FOUND, detail="Payload not found")
|
|
493
|
+
|
|
494
|
+
filename = payload.logfile
|
|
495
|
+
|
|
496
|
+
if os.path.isfile(filename):
|
|
497
|
+
return StreamingResponse(tail_log(filename), media_type="text/plain")
|
|
498
|
+
|
|
499
|
+
raise HTTPException(status_code=HTTPStatus.NOT_FOUND)
|