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
pyxecm/customizer/api/app.py
CHANGED
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
"""API Implemenation for the Customizer to start and control the payload processing.
|
|
2
|
-
|
|
3
|
-
Endpoints:
|
|
4
|
-
|
|
5
|
-
GET /app/v1/payload - get processing list of payloads
|
|
6
|
-
POST /app/v1/payload - add new payload to processing list
|
|
7
|
-
GET /api/v1/payload/{payload_id} - get a specific payload
|
|
8
|
-
GET /api/v1/payload/{payload_id}/content - get a specific payload content
|
|
9
|
-
GET /api/v1/payload/{payload_id}/log - get a specific payload content
|
|
10
|
-
GET /api/v1/payload/{payload_id}/log - get a specific payload content
|
|
11
|
-
|
|
12
|
-
"""
|
|
1
|
+
"""API Implemenation for the Customizer to start and control the payload processing."""
|
|
13
2
|
|
|
14
3
|
__author__ = "Dr. Marc Diefenbruch"
|
|
15
4
|
__copyright__ = "Copyright (C) 2024-2025, OpenText"
|
|
@@ -19,33 +8,30 @@ __email__ = "mdiefenb@opentext.com"
|
|
|
19
8
|
|
|
20
9
|
import logging
|
|
21
10
|
import os
|
|
22
|
-
import shutil
|
|
23
|
-
import signal
|
|
24
11
|
import sys
|
|
12
|
+
from collections.abc import AsyncGenerator
|
|
25
13
|
from contextlib import asynccontextmanager
|
|
26
14
|
from datetime import datetime, timezone
|
|
27
15
|
from importlib.metadata import version
|
|
28
16
|
from threading import Thread
|
|
29
|
-
from typing import Annotated, Literal
|
|
30
17
|
|
|
31
18
|
import uvicorn
|
|
32
|
-
import
|
|
33
|
-
from fastapi import Depends, FastAPI, File, Form, HTTPException, UploadFile
|
|
19
|
+
from fastapi import FastAPI
|
|
34
20
|
from fastapi.middleware.cors import CORSMiddleware
|
|
35
|
-
from fastapi.responses import FileResponse, JSONResponse, Response
|
|
36
|
-
from fastapi.security import OAuth2PasswordBearer
|
|
37
21
|
from prometheus_fastapi_instrumentator import Instrumentator
|
|
38
|
-
from pydantic import HttpUrl, ValidationError
|
|
39
22
|
|
|
40
|
-
from pyxecm.customizer import
|
|
41
|
-
from pyxecm.customizer.api import
|
|
42
|
-
from pyxecm.customizer.api.metrics import payload_logs_by_payload, payload_logs_total
|
|
43
|
-
from pyxecm.customizer.api.
|
|
23
|
+
from pyxecm.customizer.api.auth.router import router as auth_router
|
|
24
|
+
from pyxecm.customizer.api.common.functions import PAYLOAD_LIST
|
|
25
|
+
from pyxecm.customizer.api.common.metrics import payload_logs_by_payload, payload_logs_total
|
|
26
|
+
from pyxecm.customizer.api.common.router import router as common_router
|
|
44
27
|
from pyxecm.customizer.api.settings import api_settings
|
|
45
|
-
from pyxecm.customizer.
|
|
46
|
-
from pyxecm.customizer.
|
|
28
|
+
from pyxecm.customizer.api.terminal.router import router as terminal_router
|
|
29
|
+
from pyxecm.customizer.api.v1_csai.router import router as v1_csai_router
|
|
30
|
+
from pyxecm.customizer.api.v1_maintenance.router import router as v1_maintenance_router
|
|
31
|
+
from pyxecm.customizer.api.v1_otcs.router import router as v1_otcs_router
|
|
32
|
+
from pyxecm.customizer.api.v1_payload.functions import import_payload
|
|
33
|
+
from pyxecm.customizer.api.v1_payload.router import router as v1_payload_router
|
|
47
34
|
from pyxecm.maintenance_page import run_maintenance_page
|
|
48
|
-
from pyxecm.maintenance_page import settings as maint_settings
|
|
49
35
|
|
|
50
36
|
# Check if Temp dir exists
|
|
51
37
|
if not os.path.exists(api_settings.temp_dir):
|
|
@@ -60,7 +46,6 @@ if os.path.isfile(os.path.join(api_settings.logfolder, api_settings.logfile)):
|
|
|
60
46
|
elif not os.path.exists(api_settings.logfolder):
|
|
61
47
|
os.makedirs(api_settings.logfolder)
|
|
62
48
|
|
|
63
|
-
|
|
64
49
|
handlers = [
|
|
65
50
|
logging.FileHandler(os.path.join(api_settings.logfolder, api_settings.logfile)),
|
|
66
51
|
logging.StreamHandler(sys.stdout),
|
|
@@ -75,9 +60,9 @@ logging.basicConfig(
|
|
|
75
60
|
|
|
76
61
|
|
|
77
62
|
@asynccontextmanager
|
|
78
|
-
async def lifespan(
|
|
79
|
-
app: FastAPI,
|
|
80
|
-
)
|
|
63
|
+
async def lifespan(
|
|
64
|
+
app: FastAPI, # noqa: ARG001
|
|
65
|
+
) -> AsyncGenerator:
|
|
81
66
|
"""Lifespan Method for FASTAPI to handle the startup and shutdown process.
|
|
82
67
|
|
|
83
68
|
Args:
|
|
@@ -86,10 +71,10 @@ async def lifespan( # noqa: ANN201
|
|
|
86
71
|
|
|
87
72
|
"""
|
|
88
73
|
|
|
89
|
-
|
|
74
|
+
logger.debug("Settings -> %s", api_settings)
|
|
90
75
|
|
|
91
76
|
if api_settings.import_payload:
|
|
92
|
-
|
|
77
|
+
logger.info("Importing filesystem payloads...")
|
|
93
78
|
|
|
94
79
|
# Base Payload
|
|
95
80
|
import_payload(payload=api_settings.payload)
|
|
@@ -101,20 +86,20 @@ async def lifespan( # noqa: ANN201
|
|
|
101
86
|
import_payload(payload_dir=api_settings.payload_dir_optional)
|
|
102
87
|
|
|
103
88
|
if api_settings.maintenance_mode:
|
|
104
|
-
|
|
89
|
+
logger.info("Starting maintenance_page thread...")
|
|
105
90
|
maint_thread = Thread(target=run_maintenance_page, name="maintenance_page")
|
|
106
91
|
maint_thread.start()
|
|
107
92
|
|
|
108
|
-
|
|
93
|
+
logger.info("Starting processing thread...")
|
|
109
94
|
thread = Thread(
|
|
110
|
-
target=
|
|
95
|
+
target=PAYLOAD_LIST.run_payload_processing,
|
|
111
96
|
name="customization_run_api",
|
|
112
97
|
)
|
|
113
98
|
thread.start()
|
|
114
99
|
|
|
115
100
|
yield
|
|
116
|
-
|
|
117
|
-
|
|
101
|
+
logger.info("Shutdown")
|
|
102
|
+
PAYLOAD_LIST.stop_payload_processing()
|
|
118
103
|
|
|
119
104
|
|
|
120
105
|
app = FastAPI(
|
|
@@ -124,10 +109,6 @@ app = FastAPI(
|
|
|
124
109
|
lifespan=lifespan,
|
|
125
110
|
version=version("pyxecm"),
|
|
126
111
|
openapi_tags=[
|
|
127
|
-
{
|
|
128
|
-
"name": "status",
|
|
129
|
-
"description": "Status of Customizer",
|
|
130
|
-
},
|
|
131
112
|
{
|
|
132
113
|
"name": "auth",
|
|
133
114
|
"description": "Authentication Endpoint - Users are authenticated against Opentext Directory Services",
|
|
@@ -142,7 +123,20 @@ app = FastAPI(
|
|
|
142
123
|
},
|
|
143
124
|
],
|
|
144
125
|
)
|
|
145
|
-
|
|
126
|
+
|
|
127
|
+
## Add all Routers
|
|
128
|
+
app.include_router(router=common_router)
|
|
129
|
+
app.include_router(router=auth_router)
|
|
130
|
+
app.include_router(router=v1_maintenance_router)
|
|
131
|
+
app.include_router(router=v1_otcs_router)
|
|
132
|
+
app.include_router(router=v1_payload_router)
|
|
133
|
+
if api_settings.ws_terminal:
|
|
134
|
+
app.include_router(router=terminal_router)
|
|
135
|
+
if api_settings.csai:
|
|
136
|
+
app.include_router(router=v1_csai_router)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
logger = logging.getLogger("CustomizerAPI")
|
|
146
140
|
app.add_middleware(
|
|
147
141
|
CORSMiddleware,
|
|
148
142
|
allow_origins=api_settings.trusted_origins,
|
|
@@ -150,765 +144,20 @@ app.add_middleware(
|
|
|
150
144
|
allow_methods=["*"],
|
|
151
145
|
allow_headers=["*"],
|
|
152
146
|
)
|
|
153
|
-
app.k8s_object = K8s(logger=app.logger, namespace=api_settings.namespace)
|
|
154
|
-
app.include_router(auth.router)
|
|
155
|
-
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
|
156
|
-
|
|
157
|
-
# Initialize the globel Payloadlist object
|
|
158
|
-
payload_list = PayloadList(logger=app.logger)
|
|
159
147
|
|
|
160
148
|
if api_settings.metrics:
|
|
161
149
|
# Add Prometheus Instrumentator for /metrics
|
|
162
150
|
instrumentator = Instrumentator().instrument(app).expose(app)
|
|
163
|
-
instrumentator.add(payload_logs_by_payload(
|
|
164
|
-
instrumentator.add(payload_logs_total(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
@app.get(path="/status", name="Get Status", tags=["status"])
|
|
168
|
-
async def get_status() -> dict:
|
|
169
|
-
"""Get the status of the Customizer."""
|
|
170
|
-
|
|
171
|
-
df = payload_list.get_payload_items()
|
|
172
|
-
|
|
173
|
-
if df is None:
|
|
174
|
-
raise HTTPException(
|
|
175
|
-
status_code=500,
|
|
176
|
-
detail="Payload list is empty.",
|
|
177
|
-
)
|
|
178
|
-
|
|
179
|
-
all_status = df["status"].value_counts().to_dict()
|
|
180
|
-
|
|
181
|
-
return {
|
|
182
|
-
"version": "2",
|
|
183
|
-
"customizer_duration": (all_status.get("running", None)),
|
|
184
|
-
"customizer_end_time": None,
|
|
185
|
-
"customizer_start_time": None,
|
|
186
|
-
"status_details": all_status,
|
|
187
|
-
"status": "Running" if "running" in all_status else "Stopped",
|
|
188
|
-
"debug": df["log_debug"].sum(),
|
|
189
|
-
"info": df["log_info"].sum(),
|
|
190
|
-
"warning": df["log_warning"].sum(),
|
|
191
|
-
"error": df["log_error"].sum(),
|
|
192
|
-
"critical": df["log_critical"].sum(),
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def import_payload(
|
|
197
|
-
payload: str | None = None,
|
|
198
|
-
payload_dir: str | None = None,
|
|
199
|
-
enabled: bool | None = None,
|
|
200
|
-
dependencies: bool | None = None,
|
|
201
|
-
) -> None:
|
|
202
|
-
"""Automatically load payload items from disk of a given directory.
|
|
203
|
-
|
|
204
|
-
Args:
|
|
205
|
-
payload (str):
|
|
206
|
-
The name of the payload.
|
|
207
|
-
payload_dir (str):
|
|
208
|
-
The local path.
|
|
209
|
-
enabled (bool, optional):
|
|
210
|
-
Automatically start the processing (True), or only define items (False).
|
|
211
|
-
Defaults to False.
|
|
212
|
-
dependencies (bool, optional):
|
|
213
|
-
Automatically add dependency on the last payload in the queue
|
|
214
|
-
|
|
215
|
-
"""
|
|
216
|
-
|
|
217
|
-
def import_payload_file(
|
|
218
|
-
filename: str,
|
|
219
|
-
enabled: bool | None,
|
|
220
|
-
dependencies: bool | None,
|
|
221
|
-
) -> None:
|
|
222
|
-
if not os.path.isfile(filename):
|
|
223
|
-
return
|
|
224
|
-
|
|
225
|
-
if not (filename.endswith((".yaml", ".tfvars", ".tf", ".yml.gz.b64"))):
|
|
226
|
-
app.logger.debug("Skipping file: %s", filename)
|
|
227
|
-
return
|
|
228
|
-
|
|
229
|
-
# Load payload file
|
|
230
|
-
payload_content = load_payload(filename)
|
|
231
|
-
if payload_content is None:
|
|
232
|
-
exception = f"The import of payload -> {filename} failed. Payload content could not be loaded."
|
|
233
|
-
raise PayloadImportError(exception)
|
|
234
|
-
|
|
235
|
-
payload_options = payload_content.get("payloadOptions", {})
|
|
236
|
-
|
|
237
|
-
if enabled is None:
|
|
238
|
-
enabled = payload_options.get("enabled", True)
|
|
239
|
-
|
|
240
|
-
# read name from options section if specified, otherwise take filename
|
|
241
|
-
name = payload_options.get("name", os.path.basename(filename))
|
|
242
|
-
|
|
243
|
-
# Get the loglevel from payloadOptions if set, otherwise use the default loglevel
|
|
244
|
-
loglevel = payload_options.get("loglevel", api_settings.loglevel)
|
|
245
|
-
|
|
246
|
-
# Get the git_url
|
|
247
|
-
git_url = payload_options.get("git_url", None)
|
|
248
|
-
|
|
249
|
-
# Dependency Management
|
|
250
|
-
if dependencies is None:
|
|
251
|
-
dependencies = []
|
|
252
|
-
|
|
253
|
-
# Get all dependencies from payloadOptions and resolve their ID
|
|
254
|
-
for dependency_name in payload_options.get("dependencies", []):
|
|
255
|
-
dependend_item = payload_list.get_payload_item_by_name(dependency_name)
|
|
256
|
-
|
|
257
|
-
if dependend_item is None:
|
|
258
|
-
exception = (
|
|
259
|
-
f"The import of payload -> {name} failed. Dependencies cannot be resovled: {dependency_name}",
|
|
260
|
-
)
|
|
261
|
-
raise PayloadImportError(
|
|
262
|
-
exception,
|
|
263
|
-
)
|
|
264
|
-
# Add the ID to the list of dependencies
|
|
265
|
-
dependencies.append(dependend_item["index"])
|
|
266
|
-
|
|
267
|
-
elif dependencies:
|
|
268
|
-
try:
|
|
269
|
-
payload_items = len(payload_list.get_payload_items()) - 1
|
|
270
|
-
dependencies = [payload_items] if payload_items != -1 else []
|
|
271
|
-
except Exception:
|
|
272
|
-
dependencies = []
|
|
273
|
-
else:
|
|
274
|
-
dependencies = []
|
|
275
|
-
|
|
276
|
-
app.logger.info("Adding payload: %s", filename)
|
|
277
|
-
payload = payload_list.add_payload_item(
|
|
278
|
-
name=name,
|
|
279
|
-
filename=filename,
|
|
280
|
-
status="planned",
|
|
281
|
-
logfile=f"{api_settings.logfolder}/{name}.log",
|
|
282
|
-
dependencies=dependencies,
|
|
283
|
-
enabled=enabled,
|
|
284
|
-
git_url=git_url,
|
|
285
|
-
loglevel=loglevel,
|
|
286
|
-
)
|
|
287
|
-
dependencies = payload["index"]
|
|
288
|
-
|
|
289
|
-
return
|
|
290
|
-
|
|
291
|
-
if payload is None and payload_dir is None:
|
|
292
|
-
exception = "No payload or payload_dir provided"
|
|
293
|
-
raise ValueError(exception)
|
|
294
|
-
|
|
295
|
-
if payload and os.path.isdir(payload) and payload_dir is None:
|
|
296
|
-
payload_dir = payload
|
|
297
|
-
|
|
298
|
-
if payload_dir is None:
|
|
299
|
-
import_payload_file(payload, enabled, dependencies)
|
|
300
|
-
return
|
|
301
|
-
elif not os.path.isdir(payload_dir):
|
|
302
|
-
return
|
|
303
|
-
|
|
304
|
-
for filename in sorted(os.listdir(payload_dir)):
|
|
305
|
-
try:
|
|
306
|
-
import_payload_file(os.path.join(payload_dir, filename), enabled, dependencies)
|
|
307
|
-
except PayloadImportError:
|
|
308
|
-
app.logger.error("Payload import failed")
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
def prepare_dependencies(dependencies: list) -> list | None:
|
|
312
|
-
"""Convert the dependencies string to a list of integers."""
|
|
313
|
-
try:
|
|
314
|
-
list_all = dependencies[0].split(",")
|
|
315
|
-
except IndexError:
|
|
316
|
-
return None
|
|
317
|
-
|
|
318
|
-
# Remove empty values from the list
|
|
319
|
-
items = list(filter(None, list_all))
|
|
320
|
-
converted_list = []
|
|
321
|
-
for item in items:
|
|
322
|
-
try:
|
|
323
|
-
converted_list.append(int(item))
|
|
324
|
-
except ValueError:
|
|
325
|
-
continue
|
|
326
|
-
|
|
327
|
-
return converted_list
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
@app.post(path="/api/v1/payload", status_code=201, tags=["payload"])
|
|
331
|
-
def create_payload_item(
|
|
332
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)], # noqa: ARG001
|
|
333
|
-
upload_file: Annotated[UploadFile, File(...)],
|
|
334
|
-
name: Annotated[str, Form()] = "",
|
|
335
|
-
dependencies: Annotated[list[int] | list[str] | None, Form()] = None,
|
|
336
|
-
enabled: Annotated[bool, Form()] = True,
|
|
337
|
-
loglevel: Annotated[
|
|
338
|
-
Literal["DEBUG", "INFO", "WARNING"] | None,
|
|
339
|
-
Form(
|
|
340
|
-
description="Loglevel for the Payload processing",
|
|
341
|
-
),
|
|
342
|
-
] = "INFO",
|
|
343
|
-
) -> dict:
|
|
344
|
-
"""Upload a new payload item.
|
|
345
|
-
|
|
346
|
-
Args:
|
|
347
|
-
upload_file (UploadFile, optional):
|
|
348
|
-
The file to upload. Defaults to File(...).
|
|
349
|
-
name (str, optional):
|
|
350
|
-
The name of the payload (if not provided we will use the file name).
|
|
351
|
-
dependencies (list of integers):
|
|
352
|
-
List of other payload items this item depends on.
|
|
353
|
-
enabled (bool):
|
|
354
|
-
Flag indicating if the payload is enabled or not.
|
|
355
|
-
loglevel (str, optional):
|
|
356
|
-
The loglevel for the payload processing. Defaults to "INFO".
|
|
357
|
-
user (models.User, optional):
|
|
358
|
-
The user who is uploading the payload. Defaults to None.
|
|
359
|
-
|
|
360
|
-
Raises:
|
|
361
|
-
HTTPException:
|
|
362
|
-
Raised, if payload list is not initialized.
|
|
363
|
-
|
|
364
|
-
Returns:
|
|
365
|
-
dict:
|
|
366
|
-
The HTTP response.
|
|
367
|
-
|
|
368
|
-
"""
|
|
369
|
-
if dependencies:
|
|
370
|
-
dependencies = prepare_dependencies(dependencies)
|
|
371
|
-
|
|
372
|
-
# Set name if not provided
|
|
373
|
-
name = name or os.path.splitext(os.path.basename(upload_file.filename))[0]
|
|
374
|
-
file_extension = os.path.splitext(upload_file.filename)[1]
|
|
375
|
-
file_name = os.path.join(api_settings.temp_dir, f"{name}{file_extension}")
|
|
376
|
-
|
|
377
|
-
with open(file_name, "wb") as buffer:
|
|
378
|
-
shutil.copyfileobj(upload_file.file, buffer)
|
|
379
|
-
|
|
380
|
-
if dependencies == [-1]:
|
|
381
|
-
dependencies = []
|
|
382
|
-
|
|
383
|
-
return payload_list.add_payload_item(
|
|
384
|
-
name=name,
|
|
385
|
-
filename=file_name,
|
|
386
|
-
status="planned",
|
|
387
|
-
logfile=os.path.join(api_settings.temp_dir, "{}.log".format(name)),
|
|
388
|
-
dependencies=dependencies or [],
|
|
389
|
-
enabled=enabled,
|
|
390
|
-
loglevel=loglevel,
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
@app.get(path="/api/v1/payload", tags=["payload"])
|
|
395
|
-
async def get_payload_items(
|
|
396
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)], # noqa: ARG001
|
|
397
|
-
) -> dict:
|
|
398
|
-
"""Get all Payload items.
|
|
399
|
-
|
|
400
|
-
Raises:
|
|
401
|
-
HTTPException: payload list not initialized
|
|
402
|
-
HTTPException: payload list is empty
|
|
403
|
-
|
|
404
|
-
Returns:
|
|
405
|
-
dict:
|
|
406
|
-
HTTP response with the result data
|
|
407
|
-
|
|
408
|
-
"""
|
|
409
|
-
|
|
410
|
-
df = payload_list.get_payload_items()
|
|
411
|
-
|
|
412
|
-
if df is None:
|
|
413
|
-
raise HTTPException(
|
|
414
|
-
status_code=500,
|
|
415
|
-
detail="Payload list is empty.",
|
|
416
|
-
)
|
|
417
|
-
|
|
418
|
-
data = [{"index": idx, **row} for idx, row in df.iterrows()]
|
|
419
|
-
|
|
420
|
-
stats = {
|
|
421
|
-
"count": len(df),
|
|
422
|
-
"status": df["status"].value_counts().to_dict(),
|
|
423
|
-
"logs": {
|
|
424
|
-
"debug": df["log_debug"].sum(),
|
|
425
|
-
"info": df["log_info"].sum(),
|
|
426
|
-
"warning": df["log_warning"].sum(),
|
|
427
|
-
"error": df["log_error"].sum(),
|
|
428
|
-
"critical": df["log_critical"].sum(),
|
|
429
|
-
},
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return {"stats": stats, "results": data}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
@app.get(path="/api/v1/payload/{payload_id}", tags=["payload"])
|
|
436
|
-
async def get_payload_item(
|
|
437
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)], # noqa: ARG001
|
|
438
|
-
payload_id: int,
|
|
439
|
-
) -> dict:
|
|
440
|
-
"""Get a payload item based on its ID.
|
|
441
|
-
|
|
442
|
-
Args:
|
|
443
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)]
|
|
444
|
-
payload_id (int): payload item ID
|
|
445
|
-
|
|
446
|
-
Raises:
|
|
447
|
-
HTTPException: a payload item with the given ID couldn't be found
|
|
448
|
-
|
|
449
|
-
Returns:
|
|
450
|
-
dict:
|
|
451
|
-
HTTP response.
|
|
452
|
-
|
|
453
|
-
"""
|
|
454
|
-
data = payload_list.get_payload_item(index=payload_id)
|
|
455
|
-
|
|
456
|
-
if data is None:
|
|
457
|
-
raise HTTPException(
|
|
458
|
-
status_code=404,
|
|
459
|
-
detail="Payload with index -> {} not found".format(payload_id),
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
return {"index": payload_id, **data}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
@app.put(path="/api/v1/payload/{payload_id}", status_code=200, tags=["payload"])
|
|
466
|
-
async def update_payload_item(
|
|
467
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)], # noqa: ARG001
|
|
468
|
-
payload_id: int,
|
|
469
|
-
name: Annotated[str | None, Form()] = None,
|
|
470
|
-
dependencies: Annotated[list[int] | list[str] | None, Form()] = None,
|
|
471
|
-
enabled: Annotated[bool | None, Form()] = None,
|
|
472
|
-
status: Annotated[Literal["planned", "completed"] | None, Form()] = None,
|
|
473
|
-
loglevel: Annotated[
|
|
474
|
-
Literal["DEBUG", "INFO", "WARNING"] | None,
|
|
475
|
-
Form(
|
|
476
|
-
description="Loglevel for the Payload processing",
|
|
477
|
-
),
|
|
478
|
-
] = None,
|
|
479
|
-
) -> dict:
|
|
480
|
-
"""Update an existing payload item.
|
|
481
|
-
|
|
482
|
-
Args:
|
|
483
|
-
user (Optional[models.User]): User performing the update.
|
|
484
|
-
payload_id (int): ID of the payload to update.
|
|
485
|
-
upload_file (UploadFile, optional): replace the file name
|
|
486
|
-
name (Optional[str]): Updated name.
|
|
487
|
-
dependencies (Optional[List[int]]): Updated list of dependencies.
|
|
488
|
-
enabled (Optional[bool]): Updated enabled status.
|
|
489
|
-
loglevel (Optional[str]): Updated loglevel.
|
|
490
|
-
status (Optional[str]): Updated status.
|
|
491
|
-
|
|
492
|
-
Returns:
|
|
493
|
-
dict: HTTP response with the updated payload details.
|
|
494
|
-
|
|
495
|
-
"""
|
|
496
|
-
|
|
497
|
-
if dependencies:
|
|
498
|
-
dependencies = prepare_dependencies(dependencies)
|
|
499
|
-
# Check if the payload exists
|
|
500
|
-
payload_item = payload_list.get_payload_item(
|
|
501
|
-
payload_id,
|
|
502
|
-
) # Assumes a method to retrieve payload by ID
|
|
503
|
-
if payload_item is None:
|
|
504
|
-
raise HTTPException(
|
|
505
|
-
status_code=404,
|
|
506
|
-
detail="Payload with ID -> {} not found.".format(payload_id),
|
|
507
|
-
)
|
|
508
|
-
|
|
509
|
-
update_data = {}
|
|
510
|
-
|
|
511
|
-
# Update fields if provided
|
|
512
|
-
if name is not None:
|
|
513
|
-
update_data["name"] = name
|
|
514
|
-
if dependencies is not None:
|
|
515
|
-
update_data["dependencies"] = dependencies
|
|
516
|
-
if enabled is not None:
|
|
517
|
-
update_data["enabled"] = enabled
|
|
518
|
-
if status is not None:
|
|
519
|
-
update_data["status"] = status
|
|
520
|
-
if loglevel is not None:
|
|
521
|
-
update_data["loglevel"] = loglevel
|
|
522
|
-
|
|
523
|
-
if "status" in update_data and update_data["status"] == "planned":
|
|
524
|
-
app.logger.info("Resetting log message counters for -> %s", payload_id)
|
|
525
|
-
update_data["log_debug"] = 0
|
|
526
|
-
update_data["log_info"] = 0
|
|
527
|
-
update_data["log_warning"] = 0
|
|
528
|
-
update_data["log_error"] = 0
|
|
529
|
-
update_data["log_critical"] = 0
|
|
530
|
-
|
|
531
|
-
update_data["start_time"] = None
|
|
532
|
-
update_data["stop_time"] = None
|
|
533
|
-
update_data["duration"] = None
|
|
534
|
-
|
|
535
|
-
data = payload_list.get_payload_item(index=payload_id)
|
|
536
|
-
if os.path.isfile(data.logfile):
|
|
537
|
-
app.logger.info(
|
|
538
|
-
"Deleting log file (for payload) -> %s (%s)",
|
|
539
|
-
data.logfile,
|
|
540
|
-
payload_id,
|
|
541
|
-
)
|
|
542
|
-
|
|
543
|
-
now = datetime.now(timezone.utc)
|
|
544
|
-
old_log_name = (
|
|
545
|
-
os.path.dirname(data.logfile)
|
|
546
|
-
+ "/"
|
|
547
|
-
+ os.path.splitext(os.path.basename(data.logfile))[0]
|
|
548
|
-
+ now.strftime("_%Y-%m-%d_%H-%M-%S.log")
|
|
549
|
-
)
|
|
550
|
-
|
|
551
|
-
os.rename(data.logfile, old_log_name)
|
|
552
|
-
|
|
553
|
-
# Save the updated payload back to the list (or database)
|
|
554
|
-
result = payload_list.update_payload_item(
|
|
555
|
-
index=payload_id,
|
|
556
|
-
update_data=update_data,
|
|
557
|
-
) # Assumes a method to update the payload
|
|
558
|
-
if not result:
|
|
559
|
-
raise HTTPException(
|
|
560
|
-
status_code=404,
|
|
561
|
-
detail="Failed to update Payload with ID -> {} with data -> {}".format(
|
|
562
|
-
payload_id,
|
|
563
|
-
update_data,
|
|
564
|
-
),
|
|
565
|
-
)
|
|
566
|
-
|
|
567
|
-
return {
|
|
568
|
-
"message": "Payload updated successfully",
|
|
569
|
-
"payload": {**payload_list.get_payload_item(index=payload_id)},
|
|
570
|
-
"updated_fields": update_data,
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
@app.delete(path="/api/v1/payload/{payload_id}", status_code=204, tags=["payload"])
|
|
575
|
-
async def delete_payload_item(
|
|
576
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)], # noqa: ARG001
|
|
577
|
-
payload_id: int,
|
|
578
|
-
) -> JSONResponse:
|
|
579
|
-
"""Delete an existing payload item.
|
|
580
|
-
|
|
581
|
-
Args:
|
|
582
|
-
user (Optional[models.User]): User performing the update.
|
|
583
|
-
payload_id (int): The ID of the payload to update.
|
|
584
|
-
|
|
585
|
-
Returns:
|
|
586
|
-
dict: response or None
|
|
587
|
-
|
|
588
|
-
"""
|
|
589
|
-
|
|
590
|
-
# Check if the payload exists
|
|
591
|
-
result = payload_list.remove_payload_item(payload_id)
|
|
592
|
-
if not result:
|
|
593
|
-
raise HTTPException(
|
|
594
|
-
status_code=404,
|
|
595
|
-
detail="Payload with ID -> {} not found.".format(payload_id),
|
|
596
|
-
)
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
@app.put(path="/api/v1/payload/{payload_id}/up", tags=["payload"])
|
|
600
|
-
async def move_payload_item_up(
|
|
601
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)], # noqa: ARG001
|
|
602
|
-
payload_id: int,
|
|
603
|
-
) -> dict:
|
|
604
|
-
"""Move a payload item up in the list.
|
|
605
|
-
|
|
606
|
-
Args:
|
|
607
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)]
|
|
608
|
-
payload_id (int): payload item ID
|
|
609
|
-
|
|
610
|
-
Raises:
|
|
611
|
-
HTTPException: a payload item with the given ID couldn't be found
|
|
612
|
-
|
|
613
|
-
Returns:
|
|
614
|
-
dict: HTTP response
|
|
615
|
-
|
|
616
|
-
"""
|
|
617
|
-
|
|
618
|
-
position = payload_list.move_payload_item_up(index=payload_id)
|
|
619
|
-
|
|
620
|
-
if position is None:
|
|
621
|
-
raise HTTPException(
|
|
622
|
-
status_code=404,
|
|
623
|
-
detail="Payload item with index -> {} is either out of range or is already on top of the payload list!".format(
|
|
624
|
-
payload_id,
|
|
625
|
-
),
|
|
626
|
-
)
|
|
627
|
-
|
|
628
|
-
return {"result": {"new_position": position}}
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
@app.put(path="/api/v1/payload/{payload_id}/down", tags=["payload"])
|
|
632
|
-
async def move_payload_item_down(
|
|
633
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)], # noqa: ARG001
|
|
634
|
-
payload_id: int,
|
|
635
|
-
) -> dict:
|
|
636
|
-
"""Move a payload item down in the list.
|
|
637
|
-
|
|
638
|
-
Args:
|
|
639
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)]
|
|
640
|
-
payload_id (int):
|
|
641
|
-
The payload item ID.
|
|
642
|
-
|
|
643
|
-
Raises:
|
|
644
|
-
HTTPException: a payload item with the given ID couldn't be found
|
|
645
|
-
|
|
646
|
-
Returns:
|
|
647
|
-
dict: HTTP response
|
|
648
|
-
|
|
649
|
-
"""
|
|
650
|
-
|
|
651
|
-
position = payload_list.move_payload_item_down(index=payload_id)
|
|
652
|
-
|
|
653
|
-
if position is None:
|
|
654
|
-
raise HTTPException(
|
|
655
|
-
status_code=404,
|
|
656
|
-
detail="Payload item with index -> {} is either out of range or is already on bottom of the payload list!".format(
|
|
657
|
-
payload_id,
|
|
658
|
-
),
|
|
659
|
-
)
|
|
660
|
-
|
|
661
|
-
return {"result": {"new_position": position}}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
@app.get(path="/api/v1/payload/{payload_id}/content", tags=["payload"])
|
|
665
|
-
async def get_payload_content(
|
|
666
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)], # noqa: ARG001
|
|
667
|
-
# pylint: disable=unused-argument
|
|
668
|
-
payload_id: int,
|
|
669
|
-
) -> dict | None:
|
|
670
|
-
"""Get a payload item based on its ID.
|
|
671
|
-
|
|
672
|
-
Args:
|
|
673
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)]
|
|
674
|
-
payload_id (int):
|
|
675
|
-
The payload item ID.
|
|
676
|
-
|
|
677
|
-
Raises:
|
|
678
|
-
HTTPException:
|
|
679
|
-
A payload item with the given ID couldn't be found.
|
|
680
|
-
|
|
681
|
-
Returns:
|
|
682
|
-
dict:
|
|
683
|
-
HTTP response.
|
|
684
|
-
|
|
685
|
-
"""
|
|
686
|
-
|
|
687
|
-
data = payload_list.get_payload_item(index=payload_id)
|
|
688
|
-
|
|
689
|
-
if data is None:
|
|
690
|
-
raise HTTPException(
|
|
691
|
-
status_code=404,
|
|
692
|
-
detail="Payload with ID -> {} not found!".format(payload_id),
|
|
693
|
-
)
|
|
694
|
-
|
|
695
|
-
filename = data.filename
|
|
696
|
-
|
|
697
|
-
return load_payload(payload_source=filename)
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
@app.get(path="/api/v1/payload/{payload_id}/download", tags=["payload"])
|
|
701
|
-
def download_payload_content(
|
|
702
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)], # noqa: ARG001
|
|
703
|
-
payload_id: int,
|
|
704
|
-
) -> FileResponse:
|
|
705
|
-
"""Download the payload for a specific payload item."""
|
|
706
|
-
|
|
707
|
-
payload = payload_list.get_payload_item(index=payload_id)
|
|
708
|
-
|
|
709
|
-
if payload is None:
|
|
710
|
-
raise HTTPException(
|
|
711
|
-
status_code=404,
|
|
712
|
-
detail="Payload with ID -> {} not found!".format(payload_id),
|
|
713
|
-
)
|
|
714
|
-
|
|
715
|
-
if not os.path.isfile(payload.filename):
|
|
716
|
-
raise HTTPException(
|
|
717
|
-
status_code=404,
|
|
718
|
-
detail="Payload file -> '{}' not found".format(payload.filename),
|
|
719
|
-
)
|
|
720
|
-
with open(payload.filename, encoding="UTF-8") as file:
|
|
721
|
-
content = file.read()
|
|
722
|
-
return Response(
|
|
723
|
-
content,
|
|
724
|
-
media_type="application/octet-stream",
|
|
725
|
-
headers={
|
|
726
|
-
"Content-Disposition": f'attachment; filename="{os.path.basename(payload.filename)}"',
|
|
727
|
-
},
|
|
728
|
-
)
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
@app.get(path="/api/v1/payload/{payload_id}/log", tags=["payload"])
|
|
732
|
-
def download_payload_logfile(
|
|
733
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)], # noqa: ARG001
|
|
734
|
-
payload_id: int,
|
|
735
|
-
) -> FileResponse:
|
|
736
|
-
"""Download the logfile for a specific payload."""
|
|
737
|
-
|
|
738
|
-
payload = payload_list.get_payload_item(index=payload_id)
|
|
739
|
-
|
|
740
|
-
if payload is None:
|
|
741
|
-
raise HTTPException(status_code=404, detail="Payload not found")
|
|
742
|
-
|
|
743
|
-
filename = payload.logfile
|
|
744
|
-
|
|
745
|
-
if not os.path.isfile(filename):
|
|
746
|
-
raise HTTPException(
|
|
747
|
-
status_code=404,
|
|
748
|
-
detail="Log file -> '{}' not found".format(filename),
|
|
749
|
-
)
|
|
750
|
-
with open(filename, encoding="UTF-8") as file:
|
|
751
|
-
content = file.read()
|
|
752
|
-
return Response(
|
|
753
|
-
content,
|
|
754
|
-
media_type="application/octet-stream",
|
|
755
|
-
headers={
|
|
756
|
-
"Content-Disposition": f'attachment; filename="{os.path.basename(filename)}"',
|
|
757
|
-
},
|
|
758
|
-
)
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
def get_cshost() -> str:
|
|
762
|
-
"""Get the cs_hostname from the environment Variable OTCS_PUBLIC_HOST otherwise read it from the otcs-frontend-configmap."""
|
|
763
|
-
|
|
764
|
-
if "OTCS_PUBLIC_URL" in os.environ:
|
|
765
|
-
return os.getenv("OTCS_PUBLIC_URL", "otcs")
|
|
766
|
-
|
|
767
|
-
else:
|
|
768
|
-
cm = app.k8s_object.get_config_map("otcs-frontend-configmap")
|
|
769
|
-
|
|
770
|
-
if cm is None:
|
|
771
|
-
raise HTTPException(
|
|
772
|
-
status_code=500,
|
|
773
|
-
detail=f"Could not read otcs-frontend-configmap from namespace: {app.k8s_object.get_namespace()}",
|
|
774
|
-
)
|
|
775
|
-
|
|
776
|
-
config_file = cm.data.get("config.yaml")
|
|
777
|
-
config = yaml.safe_load(config_file)
|
|
778
|
-
|
|
779
|
-
try:
|
|
780
|
-
cs_url = HttpUrl(config.get("csurl"))
|
|
781
|
-
except ValidationError as ve:
|
|
782
|
-
raise HTTPException(
|
|
783
|
-
status_code=500,
|
|
784
|
-
detail="Could not read otcs_host from environment variable OTCS_PULIBC_URL or configmap otcs-frontend-configmap/config.yaml/cs_url",
|
|
785
|
-
) from ve
|
|
786
|
-
return cs_url.host
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
def __get_maintenance_mode_status() -> dict:
|
|
790
|
-
"""Get status of maintenance mode.
|
|
791
|
-
|
|
792
|
-
Returns:
|
|
793
|
-
dict:
|
|
794
|
-
Details of maintenance mode.
|
|
795
|
-
|
|
796
|
-
"""
|
|
797
|
-
ingress = app.k8s_object.get_ingress("otxecm-ingress")
|
|
798
|
-
|
|
799
|
-
if ingress is None:
|
|
800
|
-
raise HTTPException(
|
|
801
|
-
status_code=500,
|
|
802
|
-
detail="No ingress object found to read Maintenance Mode status",
|
|
803
|
-
)
|
|
804
|
-
|
|
805
|
-
enabled = False
|
|
806
|
-
for rule in ingress.spec.rules:
|
|
807
|
-
if rule.host == get_cshost():
|
|
808
|
-
enabled = rule.http.paths[0].backend.service.name != "otcs-frontend"
|
|
809
|
-
|
|
810
|
-
return {
|
|
811
|
-
"enabled": enabled,
|
|
812
|
-
"title": maint_settings.title,
|
|
813
|
-
"text": maint_settings.text,
|
|
814
|
-
"footer": maint_settings.footer,
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
@app.get(path="/api/v1/maintenance", tags=["maintenance"])
|
|
819
|
-
async def get_maintenance_mode_status(
|
|
820
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)], # noqa: ARG001
|
|
821
|
-
) -> JSONResponse:
|
|
822
|
-
"""Return status of maintenance mode.
|
|
823
|
-
|
|
824
|
-
Returns:
|
|
825
|
-
dict:
|
|
826
|
-
Details of maintenance mode.
|
|
827
|
-
|
|
828
|
-
"""
|
|
829
|
-
|
|
830
|
-
return __get_maintenance_mode_status()
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
def set_maintenance_mode_via_ingress(enabled: bool) -> None:
|
|
834
|
-
"""Set maintenance mode."""
|
|
835
|
-
|
|
836
|
-
app.logger.warning(
|
|
837
|
-
"Setting Maintenance Mode to -> %s",
|
|
838
|
-
(enabled),
|
|
839
|
-
)
|
|
840
|
-
|
|
841
|
-
if enabled:
|
|
842
|
-
app.k8s_object.update_ingress_backend_services(
|
|
843
|
-
"otxecm-ingress",
|
|
844
|
-
get_cshost(),
|
|
845
|
-
"otxecm-customizer",
|
|
846
|
-
5555,
|
|
847
|
-
)
|
|
848
|
-
else:
|
|
849
|
-
app.k8s_object.update_ingress_backend_services(
|
|
850
|
-
"otxecm-ingress",
|
|
851
|
-
get_cshost(),
|
|
852
|
-
"otcs-frontend",
|
|
853
|
-
80,
|
|
854
|
-
)
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
@app.post(path="/api/v1/maintenance", tags=["maintenance"])
|
|
858
|
-
async def set_maintenance_mode_options(
|
|
859
|
-
user: Annotated[models.User, Depends(auth.get_authorized_user)], # pylint: disable=unused-argument # noqa: ARG001
|
|
860
|
-
enabled: Annotated[bool, Form()],
|
|
861
|
-
title: Annotated[str | None, Form()] = "",
|
|
862
|
-
text: Annotated[str | None, Form()] = "",
|
|
863
|
-
footer: Annotated[str | None, Form()] = "",
|
|
864
|
-
) -> dict:
|
|
865
|
-
"""Configure the Maintenance Mode and set options.
|
|
866
|
-
|
|
867
|
-
Args:
|
|
868
|
-
user (models.User):
|
|
869
|
-
Added to enforce authentication requirement
|
|
870
|
-
enabled (bool, optional):
|
|
871
|
-
Enable or disable the maintenance mode to allow access to the OTCS Frontend.
|
|
872
|
-
title (Optional[str], optional):
|
|
873
|
-
Title for the Maintenance Page.
|
|
874
|
-
text (Optional[str], optional):
|
|
875
|
-
Text for the Maintenance Page.
|
|
876
|
-
footer (Optional[str], optional):
|
|
877
|
-
Text for the Footer of the Maintenance Page.
|
|
878
|
-
|
|
879
|
-
Returns:
|
|
880
|
-
dict: _description_
|
|
881
|
-
|
|
882
|
-
"""
|
|
883
|
-
# Enable / Disable the acutual Maintenance Mode
|
|
884
|
-
set_maintenance_mode_via_ingress(enabled)
|
|
885
|
-
|
|
886
|
-
if title is not None and title != "":
|
|
887
|
-
maint_settings.title = title
|
|
888
|
-
|
|
889
|
-
if text is not None and text != "":
|
|
890
|
-
maint_settings.text = text
|
|
891
|
-
|
|
892
|
-
if footer is not None:
|
|
893
|
-
maint_settings.footer = footer
|
|
894
|
-
|
|
895
|
-
return __get_maintenance_mode_status()
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
@app.get("/api/shutdown", include_in_schema=False)
|
|
899
|
-
def shutdown(user: Annotated[models.User, Depends(auth.get_authorized_user)]) -> JSONResponse:
|
|
900
|
-
"""Endpoint to end the application."""
|
|
901
|
-
|
|
902
|
-
app.logger.warning(
|
|
903
|
-
"Shutting down the API - Requested via api by user -> %s",
|
|
904
|
-
user.id,
|
|
905
|
-
)
|
|
906
|
-
os.kill(os.getpid(), signal.SIGTERM)
|
|
907
|
-
|
|
908
|
-
return JSONResponse()
|
|
151
|
+
instrumentator.add(payload_logs_by_payload(PAYLOAD_LIST))
|
|
152
|
+
instrumentator.add(payload_logs_total(PAYLOAD_LIST))
|
|
909
153
|
|
|
910
154
|
|
|
911
155
|
def run_api() -> None:
|
|
912
156
|
"""Start the FASTAPI Webserver."""
|
|
913
157
|
|
|
914
|
-
uvicorn.run(
|
|
158
|
+
uvicorn.run(
|
|
159
|
+
"pyxecm.customizer.api:app",
|
|
160
|
+
host=api_settings.bind_address,
|
|
161
|
+
port=api_settings.bind_port,
|
|
162
|
+
workers=api_settings.workers,
|
|
163
|
+
)
|