pyxecm 1.6__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 +7 -4
- pyxecm/avts.py +727 -254
- pyxecm/coreshare.py +686 -467
- pyxecm/customizer/__init__.py +16 -4
- pyxecm/customizer/__main__.py +58 -0
- pyxecm/customizer/api/__init__.py +5 -0
- pyxecm/customizer/api/__main__.py +6 -0
- pyxecm/customizer/api/app.py +163 -0
- pyxecm/customizer/api/auth/__init__.py +1 -0
- pyxecm/customizer/api/auth/functions.py +92 -0
- pyxecm/customizer/api/auth/models.py +13 -0
- 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/common/metrics.py +92 -0
- pyxecm/customizer/api/common/models.py +21 -0
- pyxecm/customizer/api/common/payload_list.py +870 -0
- pyxecm/customizer/api/common/router.py +72 -0
- pyxecm/customizer/api/settings.py +128 -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 +721 -286
- pyxecm/customizer/customizer.py +1076 -1425
- pyxecm/customizer/exceptions.py +35 -0
- pyxecm/customizer/guidewire.py +1186 -0
- pyxecm/customizer/k8s.py +901 -379
- pyxecm/customizer/log.py +107 -0
- pyxecm/customizer/m365.py +2967 -920
- pyxecm/customizer/nhc.py +1169 -0
- pyxecm/customizer/openapi.py +258 -0
- pyxecm/customizer/payload.py +18228 -7820
- pyxecm/customizer/pht.py +717 -286
- pyxecm/customizer/salesforce.py +516 -342
- pyxecm/customizer/sap.py +58 -41
- pyxecm/customizer/servicenow.py +611 -372
- pyxecm/customizer/settings.py +445 -0
- pyxecm/customizer/successfactors.py +408 -346
- pyxecm/customizer/translate.py +83 -48
- pyxecm/helper/__init__.py +5 -2
- pyxecm/helper/assoc.py +83 -43
- pyxecm/helper/data.py +2406 -870
- pyxecm/helper/logadapter.py +27 -0
- pyxecm/helper/web.py +229 -101
- pyxecm/helper/xml.py +596 -171
- pyxecm/maintenance_page/__init__.py +5 -0
- pyxecm/maintenance_page/__main__.py +6 -0
- pyxecm/maintenance_page/app.py +51 -0
- pyxecm/maintenance_page/settings.py +28 -0
- pyxecm/maintenance_page/static/favicon.avif +0 -0
- pyxecm/maintenance_page/templates/maintenance.html +165 -0
- pyxecm/otac.py +235 -141
- pyxecm/otawp.py +2668 -1220
- pyxecm/otca.py +569 -0
- pyxecm/otcs.py +7956 -3237
- pyxecm/otds.py +2178 -925
- pyxecm/otiv.py +36 -21
- pyxecm/otmm.py +1272 -325
- pyxecm/otpd.py +231 -127
- pyxecm-2.0.1.dist-info/METADATA +122 -0
- pyxecm-2.0.1.dist-info/RECORD +76 -0
- {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info}/WHEEL +1 -1
- pyxecm-1.6.dist-info/METADATA +0 -53
- pyxecm-1.6.dist-info/RECORD +0 -32
- {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info/licenses}/LICENSE +0 -0
- {pyxecm-1.6.dist-info → pyxecm-2.0.1.dist-info}/top_level.txt +0 -0
pyxecm/customizer/__init__.py
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
|
-
"""PYXECM classes for Customizer"""
|
|
1
|
+
"""PYXECM classes for Customizer."""
|
|
2
2
|
|
|
3
3
|
from .browser_automation import BrowserAutomation
|
|
4
|
-
|
|
5
4
|
from .customizer import Customizer
|
|
6
5
|
from .k8s import K8s
|
|
7
6
|
from .m365 import M365
|
|
8
7
|
from .payload import Payload
|
|
9
|
-
from .sap import SAP
|
|
10
8
|
from .salesforce import Salesforce
|
|
11
|
-
from .
|
|
9
|
+
from .sap import SAP
|
|
12
10
|
from .servicenow import ServiceNow
|
|
11
|
+
from .successfactors import SuccessFactors
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"M365",
|
|
15
|
+
"SAP",
|
|
16
|
+
"BrowserAutomation",
|
|
17
|
+
"Customizer",
|
|
18
|
+
"Guidewire",
|
|
19
|
+
"K8s",
|
|
20
|
+
"Payload",
|
|
21
|
+
"Salesforce",
|
|
22
|
+
"ServiceNow",
|
|
23
|
+
"SuccessFactors",
|
|
24
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Start the Customizer to process a payload."""
|
|
2
|
+
|
|
3
|
+
__author__ = "Dr. Marc Diefenbruch"
|
|
4
|
+
__copyright__ = "Copyright (C) 2024-2025, OpenText"
|
|
5
|
+
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
6
|
+
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
7
|
+
__email__ = "mdiefenb@opentext.com"
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
from pyxecm.customizer import Customizer
|
|
14
|
+
from pyxecm.customizer.payload import load_payload
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger("customizer")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def main(argv: list[str]) -> int:
|
|
20
|
+
"""Start the Customizer."""
|
|
21
|
+
|
|
22
|
+
if len(argv) < 2:
|
|
23
|
+
logger.error("No input file specified")
|
|
24
|
+
sys.exit(1)
|
|
25
|
+
|
|
26
|
+
payload_filename = argv[1]
|
|
27
|
+
|
|
28
|
+
if not os.path.isfile(payload_filename):
|
|
29
|
+
logger.error("Input file does not exist")
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
|
|
32
|
+
payload = load_payload(payload_filename)
|
|
33
|
+
customizer_settings = payload.get("customizerSettings", {})
|
|
34
|
+
|
|
35
|
+
# Overwrite the customizer settings with the payload specific ones:
|
|
36
|
+
customizer_settings.update({"cust_payload": payload_filename})
|
|
37
|
+
|
|
38
|
+
my_customizer = Customizer(logger=logger, settings=customizer_settings)
|
|
39
|
+
|
|
40
|
+
my_customizer.customization_run()
|
|
41
|
+
|
|
42
|
+
return 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if __name__ == "__main__":
|
|
46
|
+
logging.basicConfig(
|
|
47
|
+
format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
|
|
48
|
+
datefmt="%d-%b-%Y %H:%M:%S",
|
|
49
|
+
level=logging.INFO,
|
|
50
|
+
handlers=[
|
|
51
|
+
logging.StreamHandler(sys.stdout),
|
|
52
|
+
],
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
main(sys.argv)
|
|
57
|
+
except KeyboardInterrupt:
|
|
58
|
+
logger.warning("KeyboardInterrupt - exiting")
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""API Implemenation for the Customizer to start and control the payload processing."""
|
|
2
|
+
|
|
3
|
+
__author__ = "Dr. Marc Diefenbruch"
|
|
4
|
+
__copyright__ = "Copyright (C) 2024-2025, OpenText"
|
|
5
|
+
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
6
|
+
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
7
|
+
__email__ = "mdiefenb@opentext.com"
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
from collections.abc import AsyncGenerator
|
|
13
|
+
from contextlib import asynccontextmanager
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from importlib.metadata import version
|
|
16
|
+
from threading import Thread
|
|
17
|
+
|
|
18
|
+
import uvicorn
|
|
19
|
+
from fastapi import FastAPI
|
|
20
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
21
|
+
from prometheus_fastapi_instrumentator import Instrumentator
|
|
22
|
+
|
|
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
|
|
27
|
+
from pyxecm.customizer.api.settings import api_settings
|
|
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
|
|
34
|
+
from pyxecm.maintenance_page import run_maintenance_page
|
|
35
|
+
|
|
36
|
+
# Check if Temp dir exists
|
|
37
|
+
if not os.path.exists(api_settings.temp_dir):
|
|
38
|
+
os.makedirs(api_settings.temp_dir)
|
|
39
|
+
|
|
40
|
+
# Check if Logfile and folder exists and is unique
|
|
41
|
+
if os.path.isfile(os.path.join(api_settings.logfolder, api_settings.logfile)):
|
|
42
|
+
customizer_start_time = datetime.now(timezone.utc).strftime(
|
|
43
|
+
"%Y-%m-%d_%H-%M",
|
|
44
|
+
)
|
|
45
|
+
api_settings.logfile = f"customizer_{customizer_start_time}.log"
|
|
46
|
+
elif not os.path.exists(api_settings.logfolder):
|
|
47
|
+
os.makedirs(api_settings.logfolder)
|
|
48
|
+
|
|
49
|
+
handlers = [
|
|
50
|
+
logging.FileHandler(os.path.join(api_settings.logfolder, api_settings.logfile)),
|
|
51
|
+
logging.StreamHandler(sys.stdout),
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
logging.basicConfig(
|
|
55
|
+
format="%(asctime)s %(levelname)s [%(name)s] [%(threadName)s] %(message)s",
|
|
56
|
+
datefmt="%d-%b-%Y %H:%M:%S",
|
|
57
|
+
level=api_settings.loglevel,
|
|
58
|
+
handlers=handlers,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@asynccontextmanager
|
|
63
|
+
async def lifespan(
|
|
64
|
+
app: FastAPI, # noqa: ARG001
|
|
65
|
+
) -> AsyncGenerator:
|
|
66
|
+
"""Lifespan Method for FASTAPI to handle the startup and shutdown process.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
app (FastAPI):
|
|
70
|
+
The application.
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
logger.debug("Settings -> %s", api_settings)
|
|
75
|
+
|
|
76
|
+
if api_settings.import_payload:
|
|
77
|
+
logger.info("Importing filesystem payloads...")
|
|
78
|
+
|
|
79
|
+
# Base Payload
|
|
80
|
+
import_payload(payload=api_settings.payload)
|
|
81
|
+
|
|
82
|
+
# External Payload
|
|
83
|
+
import_payload(payload_dir=api_settings.payload_dir, dependencies=True)
|
|
84
|
+
|
|
85
|
+
# Optional Payload
|
|
86
|
+
import_payload(payload_dir=api_settings.payload_dir_optional)
|
|
87
|
+
|
|
88
|
+
if api_settings.maintenance_mode:
|
|
89
|
+
logger.info("Starting maintenance_page thread...")
|
|
90
|
+
maint_thread = Thread(target=run_maintenance_page, name="maintenance_page")
|
|
91
|
+
maint_thread.start()
|
|
92
|
+
|
|
93
|
+
logger.info("Starting processing thread...")
|
|
94
|
+
thread = Thread(
|
|
95
|
+
target=PAYLOAD_LIST.run_payload_processing,
|
|
96
|
+
name="customization_run_api",
|
|
97
|
+
)
|
|
98
|
+
thread.start()
|
|
99
|
+
|
|
100
|
+
yield
|
|
101
|
+
logger.info("Shutdown")
|
|
102
|
+
PAYLOAD_LIST.stop_payload_processing()
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
app = FastAPI(
|
|
106
|
+
docs_url="/api",
|
|
107
|
+
title="Customizer API",
|
|
108
|
+
openapi_url="/api/openapi.json",
|
|
109
|
+
lifespan=lifespan,
|
|
110
|
+
version=version("pyxecm"),
|
|
111
|
+
openapi_tags=[
|
|
112
|
+
{
|
|
113
|
+
"name": "auth",
|
|
114
|
+
"description": "Authentication Endpoint - Users are authenticated against Opentext Directory Services",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "payload",
|
|
118
|
+
"description": "Get status and manipulate payload objects ",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"name": "maintenance",
|
|
122
|
+
"description": "Enable, disable or alter the maintenance mode.",
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
)
|
|
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")
|
|
140
|
+
app.add_middleware(
|
|
141
|
+
CORSMiddleware,
|
|
142
|
+
allow_origins=api_settings.trusted_origins,
|
|
143
|
+
allow_credentials=True,
|
|
144
|
+
allow_methods=["*"],
|
|
145
|
+
allow_headers=["*"],
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if api_settings.metrics:
|
|
149
|
+
# Add Prometheus Instrumentator for /metrics
|
|
150
|
+
instrumentator = Instrumentator().instrument(app).expose(app)
|
|
151
|
+
instrumentator.add(payload_logs_by_payload(PAYLOAD_LIST))
|
|
152
|
+
instrumentator.add(payload_logs_total(PAYLOAD_LIST))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def run_api() -> None:
|
|
156
|
+
"""Start the FASTAPI Webserver."""
|
|
157
|
+
|
|
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
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""init module."""
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Utility library to handle the authentication with OTDS."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException, status
|
|
8
|
+
from fastapi.security import OAuth2PasswordBearer
|
|
9
|
+
|
|
10
|
+
from pyxecm.customizer.api.auth.models import User
|
|
11
|
+
from pyxecm.customizer.api.settings import api_settings
|
|
12
|
+
|
|
13
|
+
router = APIRouter()
|
|
14
|
+
|
|
15
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_groups(response: dict, token: str) -> list:
|
|
19
|
+
"""Get the groups of the user.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
response (_type_): _description_
|
|
23
|
+
token (_type_): _description_
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
list: _description_
|
|
27
|
+
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
headers = {
|
|
31
|
+
"Accept": "application/json",
|
|
32
|
+
"otdsticket": token,
|
|
33
|
+
}
|
|
34
|
+
url = api_settings.otds_url + "/otdsws/rest/users/" + response["user"]["id"] + "/memberof"
|
|
35
|
+
|
|
36
|
+
response = requests.request("GET", url, headers=headers, timeout=5)
|
|
37
|
+
if response.ok:
|
|
38
|
+
response = json.loads(response.text)
|
|
39
|
+
return [group["id"] for group in response.get("groups", [])]
|
|
40
|
+
|
|
41
|
+
# Retur empty list if request wasn't successful
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]) -> User:
|
|
46
|
+
"""Get the current user from OTDS and verify it."""
|
|
47
|
+
|
|
48
|
+
if api_settings.api_token is not None and token == api_settings.api_token:
|
|
49
|
+
return User(
|
|
50
|
+
id="api",
|
|
51
|
+
full_name="API Token",
|
|
52
|
+
groups=["otadmins@otds.admin"],
|
|
53
|
+
is_admin=True,
|
|
54
|
+
is_sysadmin=True,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
url = api_settings.otds_url + "/otdsws/rest/currentuser"
|
|
58
|
+
headers = {
|
|
59
|
+
"Accept": "application/json",
|
|
60
|
+
"otdsticket": token,
|
|
61
|
+
}
|
|
62
|
+
response = requests.request("GET", url, headers=headers, timeout=2)
|
|
63
|
+
|
|
64
|
+
if response.ok:
|
|
65
|
+
response = json.loads(response.text)
|
|
66
|
+
|
|
67
|
+
user = User(
|
|
68
|
+
id=response["user"]["id"],
|
|
69
|
+
full_name=response["user"]["name"],
|
|
70
|
+
groups=get_groups(response, token),
|
|
71
|
+
is_admin=response["isAdmin"],
|
|
72
|
+
is_sysadmin=response["isSysAdmin"],
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return user
|
|
76
|
+
else:
|
|
77
|
+
raise HTTPException(
|
|
78
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
79
|
+
detail="Invalid authentication credentials",
|
|
80
|
+
headers={"WWW-Authenticate": "Bearer"},
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def get_authorized_user(current_user: Annotated[User, Depends(get_current_user)]) -> User:
|
|
85
|
+
"""Check if the user is authorized (member of the Group otadmin@otds.admin)."""
|
|
86
|
+
|
|
87
|
+
if "otadmins@otds.admin" not in current_user.groups:
|
|
88
|
+
raise HTTPException(
|
|
89
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
90
|
+
detail=f"User {current_user.id} is not authorized",
|
|
91
|
+
)
|
|
92
|
+
return current_user
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Models for FastAPI."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class User(BaseModel):
|
|
7
|
+
"""Model for users authenticated by OTDS."""
|
|
8
|
+
|
|
9
|
+
id: str
|
|
10
|
+
full_name: str | None = None
|
|
11
|
+
groups: list[str] | None = None
|
|
12
|
+
is_admin: bool = False
|
|
13
|
+
is_sysadmin: bool = False
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Utility library to handle the authentication with OTDS."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
8
|
+
from fastapi.responses import JSONResponse
|
|
9
|
+
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
|
10
|
+
|
|
11
|
+
from pyxecm.customizer.api.auth.functions import get_current_user
|
|
12
|
+
from pyxecm.customizer.api.auth.models import User
|
|
13
|
+
from pyxecm.customizer.api.settings import api_settings
|
|
14
|
+
|
|
15
|
+
router = APIRouter()
|
|
16
|
+
|
|
17
|
+
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@router.post("/token", tags=["auth"])
|
|
21
|
+
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]) -> JSONResponse:
|
|
22
|
+
"""Login using OTDS and return a token."""
|
|
23
|
+
|
|
24
|
+
url = api_settings.otds_url + "/otdsws/rest/authentication/credentials"
|
|
25
|
+
|
|
26
|
+
payload = json.dumps(
|
|
27
|
+
{"userName": form_data.username, "password": form_data.password},
|
|
28
|
+
)
|
|
29
|
+
headers = {
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
"Accept": "application/json",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
response = requests.request(
|
|
36
|
+
"POST",
|
|
37
|
+
url,
|
|
38
|
+
headers=headers,
|
|
39
|
+
data=payload,
|
|
40
|
+
timeout=10,
|
|
41
|
+
)
|
|
42
|
+
except requests.exceptions.ConnectionError as exc:
|
|
43
|
+
raise HTTPException(
|
|
44
|
+
status_code=500,
|
|
45
|
+
detail=f"{exc.request.url} cannot be reached",
|
|
46
|
+
) from exc
|
|
47
|
+
|
|
48
|
+
if response.ok:
|
|
49
|
+
response = json.loads(response.text)
|
|
50
|
+
else:
|
|
51
|
+
raise HTTPException(status_code=400, detail="Incorrect username or password")
|
|
52
|
+
|
|
53
|
+
return JSONResponse(
|
|
54
|
+
{
|
|
55
|
+
"access_token": response["ticket"],
|
|
56
|
+
"token_type": "bearer",
|
|
57
|
+
"userId": response["userId"],
|
|
58
|
+
},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@router.get("/users/me", tags=["auth"])
|
|
63
|
+
async def read_users_me(current_user: Annotated[User, Depends(get_current_user)]) -> JSONResponse:
|
|
64
|
+
"""Get the current user.
|
|
65
|
+
|
|
66
|
+
current_user:
|
|
67
|
+
type: User
|
|
68
|
+
description: The current user.
|
|
69
|
+
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
if "otadmins@otds.admin" in current_user.groups:
|
|
73
|
+
return JSONResponse(current_user.model_dump())
|
|
74
|
+
else:
|
|
75
|
+
raise HTTPException(
|
|
76
|
+
status_code=403,
|
|
77
|
+
detail=f"User {current_user.id} is not authorized",
|
|
78
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""init module."""
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Define common functions."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from pyxecm.customizer.api.common.payload_list import PayloadList
|
|
6
|
+
from pyxecm.customizer.api.settings import CustomizerAPISettings, api_settings
|
|
7
|
+
from pyxecm.customizer.k8s import K8s
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("pyxecm.customizer.api")
|
|
10
|
+
|
|
11
|
+
# Create a LOCK dict for singleton logs collection
|
|
12
|
+
LOGS_LOCK = {}
|
|
13
|
+
# Initialize the globel Payloadlist object
|
|
14
|
+
PAYLOAD_LIST = PayloadList(logger=logger)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def get_k8s_object() -> K8s:
|
|
18
|
+
"""Get an instance of a K8s object.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
K8s: Return a K8s object
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
return K8s(logger=logger, namespace=api_settings.namespace)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_settings() -> CustomizerAPISettings:
|
|
29
|
+
"""Get the API Settings object.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
CustomizerPISettings: Returns the API Settings
|
|
33
|
+
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
return api_settings
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_otcs_logs_lock() -> dict:
|
|
40
|
+
"""Get the Logs LOCK dict.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
The dict with all LOCKS for the logs
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
return LOGS_LOCK
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""Metics for payload logs."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
from prometheus_client import Gauge
|
|
6
|
+
from prometheus_fastapi_instrumentator.metrics import Info
|
|
7
|
+
|
|
8
|
+
from pyxecm.customizer.api.common.payload_list import PayloadList
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
## By Payload
|
|
12
|
+
def payload_logs_by_payload(payload_list: PayloadList) -> Callable[[Info], None]:
|
|
13
|
+
"""Metrics for payload logs by payload."""
|
|
14
|
+
|
|
15
|
+
metrics_error = Gauge(
|
|
16
|
+
"payload_error",
|
|
17
|
+
"Number of ERROR log messages for by payload",
|
|
18
|
+
labelnames=("index", "name", "logfile"),
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
metrics_warning = Gauge(
|
|
22
|
+
"payload_warning",
|
|
23
|
+
"Number of WARNING log messages for by payload",
|
|
24
|
+
labelnames=("index", "name", "logfile"),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
metrics_info = Gauge(
|
|
28
|
+
"payload_info",
|
|
29
|
+
"Number of INFO log messages for by payload",
|
|
30
|
+
labelnames=("index", "name", "logfile"),
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
metrics_debug = Gauge(
|
|
34
|
+
"payload_debug",
|
|
35
|
+
"Number of DEBUG log messages for by payload",
|
|
36
|
+
labelnames=("index", "name", "logfile"),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def instrumentation(info: Info) -> None: # noqa: ARG001
|
|
40
|
+
df = payload_list.get_payload_items()
|
|
41
|
+
data = [{"index": idx, **row} for idx, row in df.iterrows()]
|
|
42
|
+
|
|
43
|
+
for item in data:
|
|
44
|
+
metrics_error.labels(item["index"], item["name"], item["logfile"]).set(
|
|
45
|
+
item["log_error"],
|
|
46
|
+
)
|
|
47
|
+
metrics_warning.labels(item["index"], item["name"], item["logfile"]).set(
|
|
48
|
+
item["log_warning"],
|
|
49
|
+
)
|
|
50
|
+
metrics_info.labels(item["index"], item["name"], item["logfile"]).set(
|
|
51
|
+
item["log_info"],
|
|
52
|
+
)
|
|
53
|
+
metrics_debug.labels(item["index"], item["name"], item["logfile"]).set(
|
|
54
|
+
item["log_debug"],
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return instrumentation
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
## Total
|
|
61
|
+
def payload_logs_total(payload_list: PayloadList) -> Callable[[Info], None]:
|
|
62
|
+
"""Metrics for total payload logs messages."""
|
|
63
|
+
|
|
64
|
+
metrics_error = Gauge(
|
|
65
|
+
"payload_error_total",
|
|
66
|
+
"Total number of ERROR log messages",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
metrics_warning = Gauge(
|
|
70
|
+
"payload_warning_total",
|
|
71
|
+
"Total number of WARNING log messages",
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
metrics_info = Gauge(
|
|
75
|
+
"payload_info_total",
|
|
76
|
+
"Total number of INFO log messages",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
metrics_debug = Gauge(
|
|
80
|
+
"payload_debug_total",
|
|
81
|
+
"Total number of DEBUG log messages",
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def instrumentation(info: Info) -> None: # noqa: ARG001
|
|
85
|
+
df = payload_list.get_payload_items()
|
|
86
|
+
|
|
87
|
+
metrics_error.set(df["log_error"].sum())
|
|
88
|
+
metrics_warning.set(df["log_warning"].sum())
|
|
89
|
+
metrics_info.set(df["log_info"].sum())
|
|
90
|
+
metrics_debug.set(df["log_debug"].sum())
|
|
91
|
+
|
|
92
|
+
return instrumentation
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Define common base Models."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class CustomizerStatus(BaseModel):
|
|
9
|
+
"""Define Model for Customizer Status."""
|
|
10
|
+
|
|
11
|
+
version: int = 2
|
|
12
|
+
customizer_duration: Any | None
|
|
13
|
+
customizer_end_time: Any | None
|
|
14
|
+
customizer_start_time: Any | None
|
|
15
|
+
status_details: dict
|
|
16
|
+
status: str = "Stopped"
|
|
17
|
+
debug: int = 0
|
|
18
|
+
info: int = 0
|
|
19
|
+
warning: int = 0
|
|
20
|
+
error: int = 0
|
|
21
|
+
critical: int = 0
|