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/otawp.py
CHANGED
|
@@ -1,42 +1,313 @@
|
|
|
1
|
-
"""
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
""
|
|
1
|
+
"""Synchronize AppWorks projects, publsh and create run time instances for that."""
|
|
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"
|
|
5
8
|
|
|
6
|
-
import logging
|
|
7
|
-
import xml.etree.ElementTree as ET
|
|
8
|
-
import uuid
|
|
9
9
|
import json
|
|
10
|
+
import logging
|
|
11
|
+
import platform
|
|
10
12
|
import re
|
|
13
|
+
import sys
|
|
11
14
|
import time
|
|
15
|
+
import uuid
|
|
16
|
+
from http import HTTPStatus
|
|
17
|
+
from importlib.metadata import version
|
|
18
|
+
|
|
12
19
|
import requests
|
|
13
|
-
from .otds import OTDS
|
|
14
20
|
|
|
15
|
-
|
|
21
|
+
from pyxecm.helper.xml import XML
|
|
22
|
+
from pyxecm.otds import OTDS
|
|
23
|
+
|
|
24
|
+
APP_NAME = "pyxecm"
|
|
25
|
+
APP_VERSION = version("pyxecm")
|
|
26
|
+
MODULE_NAME = APP_NAME + ".otawp"
|
|
27
|
+
|
|
28
|
+
PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
29
|
+
OS_INFO = f"{platform.system()} {platform.release()}"
|
|
30
|
+
ARCH_INFO = platform.machine()
|
|
31
|
+
REQUESTS_VERSION = requests.__version__
|
|
16
32
|
|
|
17
|
-
|
|
33
|
+
USER_AGENT = (
|
|
34
|
+
f"{APP_NAME}/{APP_VERSION} ({MODULE_NAME}/{APP_VERSION}; "
|
|
35
|
+
f"Python/{PYTHON_VERSION}; {OS_INFO}; {ARCH_INFO}; Requests/{REQUESTS_VERSION})"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
REQUEST_HEADERS_XML = {
|
|
39
|
+
"User-Agent": USER_AGENT,
|
|
18
40
|
"Content-Type": "text/xml; charset=utf-8",
|
|
19
|
-
"accept": "application/xml"
|
|
41
|
+
"accept": "application/xml",
|
|
20
42
|
}
|
|
21
43
|
|
|
22
44
|
REQUEST_FORM_HEADERS = {
|
|
45
|
+
"User-Agent": USER_AGENT,
|
|
23
46
|
"accept": "application/xml;charset=utf-8",
|
|
24
47
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
25
48
|
}
|
|
26
49
|
|
|
27
50
|
REQUEST_HEADERS_JSON = {
|
|
51
|
+
"User-Agent": USER_AGENT,
|
|
28
52
|
"Content-Type": "application/json; charset=utf-8",
|
|
29
|
-
"accept": "application/json"
|
|
53
|
+
"accept": "application/json",
|
|
30
54
|
}
|
|
31
|
-
|
|
55
|
+
|
|
56
|
+
REQUEST_TIMEOUT = 120
|
|
57
|
+
REQUEST_MAX_RETRIES = 10
|
|
58
|
+
REQUEST_RETRY_DELAY = 30
|
|
59
|
+
SYNC_PUBLISH_REQUEST_TIMEOUT = 300
|
|
60
|
+
|
|
61
|
+
default_logger = logging.getLogger(MODULE_NAME)
|
|
62
|
+
|
|
63
|
+
SOAP_FAULT_INDICATOR = "Fault"
|
|
64
|
+
|
|
32
65
|
|
|
33
66
|
class OTAWP:
|
|
34
|
-
"""
|
|
67
|
+
"""Class OTAWP is used to automate settings in OpenText AppWorks Platform (OTAWP)."""
|
|
68
|
+
|
|
69
|
+
logger: logging.Logger = default_logger
|
|
70
|
+
|
|
35
71
|
_config: dict
|
|
36
72
|
_config = None
|
|
37
73
|
_cookie = None
|
|
38
74
|
_otawp_ticket = None
|
|
39
75
|
|
|
76
|
+
@classmethod
|
|
77
|
+
def resource_payload(
|
|
78
|
+
cls,
|
|
79
|
+
org_name: str,
|
|
80
|
+
username: str,
|
|
81
|
+
password: str,
|
|
82
|
+
) -> dict:
|
|
83
|
+
"""Create data structure for OTDS resource settings we need for AppWorks.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
org_name (str):
|
|
87
|
+
The name of the organization.
|
|
88
|
+
username (str):
|
|
89
|
+
The user name.
|
|
90
|
+
password (str):
|
|
91
|
+
The password.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
dict:
|
|
95
|
+
AppWorks specific payload.
|
|
96
|
+
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
additional_payload = {}
|
|
100
|
+
additional_payload["connectorid"] = "rest"
|
|
101
|
+
additional_payload["resourceType"] = "rest"
|
|
102
|
+
user_attribute_mapping = [
|
|
103
|
+
{
|
|
104
|
+
"sourceAttr": ["oTExternalID1"],
|
|
105
|
+
"destAttr": "__NAME__",
|
|
106
|
+
"mappingFormat": "%s",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"sourceAttr": ["displayname"],
|
|
110
|
+
"destAttr": "DisplayName",
|
|
111
|
+
"mappingFormat": "%s",
|
|
112
|
+
},
|
|
113
|
+
{"sourceAttr": ["mail"], "destAttr": "Email", "mappingFormat": "%s"},
|
|
114
|
+
{
|
|
115
|
+
"sourceAttr": ["oTTelephoneNumber"],
|
|
116
|
+
"destAttr": "Telephone",
|
|
117
|
+
"mappingFormat": "%s",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"sourceAttr": ["oTMobile"],
|
|
121
|
+
"destAttr": "Mobile",
|
|
122
|
+
"mappingFormat": "%s",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"sourceAttr": ["oTFacsimileTelephoneNumber"],
|
|
126
|
+
"destAttr": "Fax",
|
|
127
|
+
"mappingFormat": "%s",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"sourceAttr": ["oTStreetAddress,l,st,postalCode,c"],
|
|
131
|
+
"destAttr": "Address",
|
|
132
|
+
"mappingFormat": "%s%n%s %s %s%n%s",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"sourceAttr": ["oTCompany"],
|
|
136
|
+
"destAttr": "Company",
|
|
137
|
+
"mappingFormat": "%s",
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
"sourceAttr": ["ds-pwp-account-disabled"],
|
|
141
|
+
"destAttr": "AccountDisabled",
|
|
142
|
+
"mappingFormat": "%s",
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"sourceAttr": ["oTExtraAttr9"],
|
|
146
|
+
"destAttr": "IsServiceAccount",
|
|
147
|
+
"mappingFormat": "%s",
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
"sourceAttr": ["custom:proxyConfiguration"],
|
|
151
|
+
"destAttr": "ProxyConfiguration",
|
|
152
|
+
"mappingFormat": "%s",
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"sourceAttr": ["c"],
|
|
156
|
+
"destAttr": "Identity-CountryOrRegion",
|
|
157
|
+
"mappingFormat": "%s",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
"sourceAttr": ["gender"],
|
|
161
|
+
"destAttr": "Identity-Gender",
|
|
162
|
+
"mappingFormat": "%s",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"sourceAttr": ["displayName"],
|
|
166
|
+
"destAttr": "Identity-DisplayName",
|
|
167
|
+
"mappingFormat": "%s",
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"sourceAttr": ["oTStreetAddress"],
|
|
171
|
+
"destAttr": "Identity-Address",
|
|
172
|
+
"mappingFormat": "%s",
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"sourceAttr": ["l"],
|
|
176
|
+
"destAttr": "Identity-City",
|
|
177
|
+
"mappingFormat": "%s",
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"sourceAttr": ["mail"],
|
|
181
|
+
"destAttr": "Identity-Email",
|
|
182
|
+
"mappingFormat": "%s",
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
"sourceAttr": ["givenName"],
|
|
186
|
+
"destAttr": "Identity-FirstName",
|
|
187
|
+
"mappingFormat": "%s",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"sourceAttr": ["sn"],
|
|
191
|
+
"destAttr": "Identity-LastName",
|
|
192
|
+
"mappingFormat": "%s",
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"sourceAttr": ["initials"],
|
|
196
|
+
"destAttr": "Identity-MiddleNames",
|
|
197
|
+
"mappingFormat": "%s",
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"sourceAttr": ["oTMobile"],
|
|
201
|
+
"destAttr": "Identity-Mobile",
|
|
202
|
+
"mappingFormat": "%s",
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
"sourceAttr": ["postalCode"],
|
|
206
|
+
"destAttr": "Identity-PostalCode",
|
|
207
|
+
"mappingFormat": "%s",
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"sourceAttr": ["st"],
|
|
211
|
+
"destAttr": "Identity-StateOrProvince",
|
|
212
|
+
"mappingFormat": "%s",
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"sourceAttr": ["title"],
|
|
216
|
+
"destAttr": "Identity-title",
|
|
217
|
+
"mappingFormat": "%s",
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"sourceAttr": ["physicalDeliveryOfficeName"],
|
|
221
|
+
"destAttr": "Identity-physicalDeliveryOfficeName",
|
|
222
|
+
"mappingFormat": "%s",
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
"sourceAttr": ["oTFacsimileTelephoneNumber"],
|
|
226
|
+
"destAttr": "Identity-oTFacsimileTelephoneNumber",
|
|
227
|
+
"mappingFormat": "%s",
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
"sourceAttr": ["notes"],
|
|
231
|
+
"destAttr": "Identity-notes",
|
|
232
|
+
"mappingFormat": "%s",
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"sourceAttr": ["oTCompany"],
|
|
236
|
+
"destAttr": "Identity-oTCompany",
|
|
237
|
+
"mappingFormat": "%s",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"sourceAttr": ["oTDepartment"],
|
|
241
|
+
"destAttr": "Identity-oTDepartment",
|
|
242
|
+
"mappingFormat": "%s",
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
"sourceAttr": ["birthDate"],
|
|
246
|
+
"destAttr": "Identity-Birthday",
|
|
247
|
+
"mappingFormat": "%s",
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
"sourceAttr": ["cn"],
|
|
251
|
+
"destAttr": "Identity-UserName",
|
|
252
|
+
"mappingFormat": "%s",
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"sourceAttr": ["Description"],
|
|
256
|
+
"destAttr": "Identity-UserDescription",
|
|
257
|
+
"mappingFormat": "%s",
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"sourceAttr": ["oTTelephoneNumber"],
|
|
261
|
+
"destAttr": "Identity-Phone",
|
|
262
|
+
"mappingFormat": "%s",
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
"sourceAttr": ["displayName"],
|
|
266
|
+
"destAttr": "Identity-IdentityDisplayName",
|
|
267
|
+
"mappingFormat": "%s",
|
|
268
|
+
},
|
|
269
|
+
]
|
|
270
|
+
additional_payload["userAttributeMapping"] = user_attribute_mapping
|
|
271
|
+
group_attribute_mapping = [
|
|
272
|
+
{
|
|
273
|
+
"sourceAttr": ["cn"],
|
|
274
|
+
"destAttr": "__NAME__",
|
|
275
|
+
"mappingFormat": '%js:function format(name) { return name.replace(/&/g,"-and-"); }',
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
"sourceAttr": ["description"],
|
|
279
|
+
"destAttr": "Description",
|
|
280
|
+
"mappingFormat": "%s",
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
"sourceAttr": ["description"],
|
|
284
|
+
"destAttr": "Identity-Description",
|
|
285
|
+
"mappingFormat": "%s",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"sourceAttr": ["displayName"],
|
|
289
|
+
"destAttr": "Identity-DisplayName",
|
|
290
|
+
"mappingFormat": "%s",
|
|
291
|
+
},
|
|
292
|
+
]
|
|
293
|
+
additional_payload["groupAttributeMapping"] = group_attribute_mapping
|
|
294
|
+
additional_payload["connectorName"] = "REST (Generic)"
|
|
295
|
+
additional_payload["pcCreatePermissionAllowed"] = "true"
|
|
296
|
+
additional_payload["pcModifyPermissionAllowed"] = "true"
|
|
297
|
+
additional_payload["pcDeletePermissionAllowed"] = "false"
|
|
298
|
+
additional_payload["connectionParamInfo"] = [
|
|
299
|
+
{
|
|
300
|
+
"name": "fBaseURL",
|
|
301
|
+
"value": "http://appworks:8080/home/" + org_name + "/app/otdspush",
|
|
302
|
+
},
|
|
303
|
+
{"name": "fUsername", "value": username},
|
|
304
|
+
{"name": "fPassword", "value": password},
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
return additional_payload
|
|
308
|
+
|
|
309
|
+
# end method definition
|
|
310
|
+
|
|
40
311
|
def __init__(
|
|
41
312
|
self,
|
|
42
313
|
protocol: str,
|
|
@@ -44,8 +315,49 @@ class OTAWP:
|
|
|
44
315
|
port: int,
|
|
45
316
|
username: str | None = None,
|
|
46
317
|
password: str | None = None,
|
|
318
|
+
organization: str | None = None,
|
|
47
319
|
otawp_ticket: str | None = None,
|
|
48
|
-
|
|
320
|
+
config_map_name: str | None = None,
|
|
321
|
+
license_file: str | None = None,
|
|
322
|
+
product_name: str | None = None,
|
|
323
|
+
product_description: str | None = None,
|
|
324
|
+
logger: logging.Logger = default_logger,
|
|
325
|
+
) -> None:
|
|
326
|
+
"""Initialize OTAWP (AppWorks Platform) object.
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
protocol (str):
|
|
330
|
+
Either http or https.
|
|
331
|
+
hostname (str):
|
|
332
|
+
The hostname of Extended ECM server to communicate with.
|
|
333
|
+
port (int):
|
|
334
|
+
The port number used to talk to the Extended ECM server.
|
|
335
|
+
username (str, optional):
|
|
336
|
+
The admin user name of OTAWP. Optional if otawp_ticket is provided.
|
|
337
|
+
password (str, optional):
|
|
338
|
+
The admin password of OTAWP. Optional if otawp_ticket is provided.
|
|
339
|
+
organization (str, optional):
|
|
340
|
+
The AppWorks organization. Used in LDAP strings and base URL.
|
|
341
|
+
otawp_ticket (str, optional):
|
|
342
|
+
The authentication ticket of OTAWP.
|
|
343
|
+
config_map_name (str | None, optional):
|
|
344
|
+
The AppWorks Kubernetes Config Map name. Defaults to None.
|
|
345
|
+
license_file (str | None, optional):
|
|
346
|
+
The file name and path to the license file for AppWorks. Defaults to None.
|
|
347
|
+
product_name (str | None, optional):
|
|
348
|
+
The product name for OTAWP used in the OTDS license. Defaults to None.
|
|
349
|
+
product_description (str | None, optional):
|
|
350
|
+
The product description in the OTDS license. Defaults to None.
|
|
351
|
+
logger (logging.Logger, optional):
|
|
352
|
+
The logging object to use for all log messages. Defaults to default_logger.
|
|
353
|
+
|
|
354
|
+
"""
|
|
355
|
+
|
|
356
|
+
if logger != default_logger:
|
|
357
|
+
self.logger = logger.getChild("otawp")
|
|
358
|
+
for logfilter in logger.filters:
|
|
359
|
+
self.logger.addFilter(logfilter)
|
|
360
|
+
|
|
49
361
|
otawp_config = {}
|
|
50
362
|
|
|
51
363
|
otawp_config["hostname"] = hostname if hostname else "appworks"
|
|
@@ -53,125 +365,231 @@ class OTAWP:
|
|
|
53
365
|
otawp_config["port"] = port if port else 8080
|
|
54
366
|
otawp_config["username"] = username if username else "sysadmin"
|
|
55
367
|
otawp_config["password"] = password if password else ""
|
|
368
|
+
otawp_config["organization"] = organization if organization else "system"
|
|
369
|
+
otawp_config["configMapName"] = config_map_name if config_map_name else ""
|
|
370
|
+
otawp_config["licenseFile"] = license_file if license_file else ""
|
|
371
|
+
otawp_config["productName"] = product_name if product_name else "APPWORKS_PLATFORM"
|
|
372
|
+
otawp_config["productDescription"] = (
|
|
373
|
+
product_description if product_description else "OpenText Appworks Platform"
|
|
374
|
+
)
|
|
56
375
|
|
|
57
376
|
if otawp_ticket:
|
|
377
|
+
self._otawp_ticket = otawp_ticket
|
|
58
378
|
self._cookie = {"defaultinst_SAMLart": otawp_ticket}
|
|
59
379
|
|
|
60
|
-
|
|
380
|
+
server_url = "{}://{}".format(protocol, otawp_config["hostname"])
|
|
61
381
|
if str(port) not in ["80", "443"]:
|
|
62
|
-
|
|
63
|
-
otds_base_url += "/home/system"
|
|
382
|
+
server_url += ":{}".format(port)
|
|
64
383
|
|
|
65
|
-
otawp_config["
|
|
66
|
-
otds_base_url
|
|
67
|
-
+ "/com.eibus.web.soap.Gateway.wcp?organization=o=system,cn=cordys,cn=defaultInst,o=opentext.net"
|
|
68
|
-
)
|
|
384
|
+
otawp_config["serverUrl"] = server_url
|
|
69
385
|
|
|
70
|
-
|
|
71
|
-
otds_base_url
|
|
72
|
-
+ "/com.eibus.web.soap.Gateway.wcp?organization=o=system,cn=cordys,cn=defaultInst,o=opentext.net&defaultinst_ct=abcd"
|
|
73
|
-
)
|
|
386
|
+
self._config = otawp_config
|
|
74
387
|
|
|
75
|
-
otawp_config["
|
|
76
|
-
otds_base_url
|
|
77
|
-
+ "/app/entityRestService/api/OpentextCaseManagement/entities/Priority?defaultinst_ct=abcd"
|
|
78
|
-
)
|
|
79
|
-
otawp_config["getAllPriorities"] = (
|
|
80
|
-
otds_base_url
|
|
81
|
-
+ "/app/entityRestService/api/OpentextCaseManagement/entities/Priority/lists/PriorityList"
|
|
82
|
-
)
|
|
388
|
+
self.set_organization(otawp_config["organization"])
|
|
83
389
|
|
|
84
|
-
|
|
85
|
-
otds_base_url
|
|
86
|
-
+ "/app/entityRestService/api/OpentextCaseManagement/entities/Customer?defaultinst_ct=abcd"
|
|
87
|
-
)
|
|
88
|
-
otawp_config["getAllCustomeres"] = (
|
|
89
|
-
otds_base_url
|
|
90
|
-
+ "/app/entityRestService/api/OpentextCaseManagement/entities/Customer/lists/CustomerList"
|
|
91
|
-
)
|
|
390
|
+
# end method definition
|
|
92
391
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
+ "/app/entityRestService/api/OpentextCaseManagement/entities/CaseType?defaultinst_ct=abcd"
|
|
96
|
-
)
|
|
97
|
-
otawp_config["getAllCaseTypes"] = (
|
|
98
|
-
otds_base_url
|
|
99
|
-
+ "/app/entityRestService/api/OpentextCaseManagement/entities/CaseType/lists/AllCaseTypes"
|
|
100
|
-
)
|
|
392
|
+
def server_url(self) -> str:
|
|
393
|
+
"""Return AppWorks server information.
|
|
101
394
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
)
|
|
106
|
-
otawp_config["getAllCategories"] = (
|
|
107
|
-
otds_base_url
|
|
108
|
-
+ "/app/entityRestService/api/OpentextCaseManagement/entities/Category/lists/CategoryList"
|
|
109
|
-
)
|
|
395
|
+
Returns:
|
|
396
|
+
str:
|
|
397
|
+
Server configuration.
|
|
110
398
|
|
|
111
|
-
|
|
112
|
-
otds_base_url
|
|
113
|
-
+ "/app/entityRestService/api/OpentextCaseManagement/entities/Source"
|
|
114
|
-
)
|
|
399
|
+
"""
|
|
115
400
|
|
|
116
|
-
|
|
117
|
-
otds_base_url
|
|
118
|
-
+ "/app/entityRestService/api/OpentextCaseManagement/entities/Source/lists/AllSources"
|
|
119
|
-
)
|
|
401
|
+
return self.config()["server"]
|
|
120
402
|
|
|
121
|
-
|
|
122
|
-
otds_base_url
|
|
123
|
-
+ "/app/entityRestService/api/OpentextCaseManagement/entities/Category/childEntities/SubCategory/lists/AllSubcategories"
|
|
124
|
-
)
|
|
403
|
+
# end method definition
|
|
125
404
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
405
|
+
def set_organization(self, organization: str) -> None:
|
|
406
|
+
"""Set the AppWorks organization context.
|
|
407
|
+
|
|
408
|
+
This requires to also update all URLs that are including
|
|
409
|
+
the organization.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
organization (str):
|
|
413
|
+
The AppWorks organization name.
|
|
414
|
+
|
|
415
|
+
"""
|
|
416
|
+
|
|
417
|
+
self._config["organization"] = organization
|
|
418
|
+
|
|
419
|
+
otawp_base_url = self._config["serverUrl"] + "/home/{}".format(self._config["organization"])
|
|
420
|
+
self._config["baseUrl"] = otawp_base_url
|
|
421
|
+
|
|
422
|
+
ldap_root = "organization=o={},cn=cordys,cn=defaultInst,o=opentext.net".format(self._config["organization"])
|
|
423
|
+
self._config["gatewayAuthenticationUrl"] = otawp_base_url + "/com.eibus.web.soap.Gateway.wcp?" + ldap_root
|
|
424
|
+
|
|
425
|
+
self._config["soapGatewayUrl"] = self._config["gatewayAuthenticationUrl"] + "&defaultinst_ct=abcd"
|
|
426
|
+
|
|
427
|
+
self._config["entityUrl"] = otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities"
|
|
428
|
+
|
|
429
|
+
self._config["priorityUrl"] = self._config["entityUrl"] + "/Priority"
|
|
430
|
+
self._config["priorityListUrl"] = self._config["priorityUrl"] + "/lists/PriorityList"
|
|
431
|
+
|
|
432
|
+
self._config["customerUrl"] = self._config["entityUrl"] + "/Customer"
|
|
433
|
+
self._config["customerListUrl"] = self._config["customerUrl"] + "/lists/CustomerList"
|
|
434
|
+
|
|
435
|
+
self._config["caseTypeUrl"] = self._config["entityUrl"] + "/CaseType"
|
|
436
|
+
self._config["caseTypeListUrl"] = self._config["caseTypeUrl"] + "/lists/AllCaseTypes"
|
|
437
|
+
|
|
438
|
+
self._config["categoryUrl"] = self._config["entityUrl"] + "/Category"
|
|
439
|
+
self._config["categoryListUrl"] = self._config["categoryUrl"] + "/lists/CategoryList"
|
|
440
|
+
|
|
441
|
+
self._config["subCategoryListUrl"] = (
|
|
442
|
+
self._config["categoryUrl"] + "/childEntities/SubCategory/lists/AllSubcategories"
|
|
137
443
|
)
|
|
138
|
-
|
|
444
|
+
|
|
445
|
+
self._config["sourceUrl"] = self._config["entityUrl"] + "/Source"
|
|
446
|
+
self._config["sourceListUrl"] = self._config["sourceUrl"] + "/lists/AllSources"
|
|
447
|
+
|
|
448
|
+
self._config["caseUrl"] = self._config["entityUrl"] + "/Case"
|
|
449
|
+
self._config["caseListUrl"] = self._config["caseUrl"] + "/lists/AllCasesList"
|
|
450
|
+
|
|
451
|
+
self.logger.info("AppWorks organization set to -> '%s'.", organization)
|
|
139
452
|
|
|
140
453
|
# end method definition
|
|
141
454
|
|
|
142
|
-
def
|
|
143
|
-
"""
|
|
455
|
+
def base_url(self) -> str:
|
|
456
|
+
"""Return the base URL of AppWorks.
|
|
457
|
+
|
|
144
458
|
Returns:
|
|
145
|
-
|
|
459
|
+
str:
|
|
460
|
+
The base URL of AppWorks Platform.
|
|
461
|
+
|
|
462
|
+
"""
|
|
463
|
+
|
|
464
|
+
return self.config()["baseUrl"]
|
|
465
|
+
|
|
466
|
+
# end method definition
|
|
467
|
+
|
|
468
|
+
def license_file(self) -> str:
|
|
469
|
+
"""Return the AppWorks license file.
|
|
470
|
+
|
|
471
|
+
Returns:
|
|
472
|
+
str:
|
|
473
|
+
The name (including path) of the AppWorks license file.
|
|
474
|
+
|
|
475
|
+
"""
|
|
476
|
+
|
|
477
|
+
return self.config()["licenseFile"]
|
|
478
|
+
|
|
479
|
+
# end method definition
|
|
480
|
+
|
|
481
|
+
def product_name(self) -> str:
|
|
482
|
+
"""Return the AppWorks product name as used in the OTDS license.
|
|
483
|
+
|
|
484
|
+
Returns:
|
|
485
|
+
str:
|
|
486
|
+
The AppWorks product name.
|
|
487
|
+
|
|
488
|
+
"""
|
|
489
|
+
|
|
490
|
+
return self.config()["productName"]
|
|
491
|
+
|
|
492
|
+
# end method definition
|
|
493
|
+
|
|
494
|
+
def product_description(self) -> str:
|
|
495
|
+
"""Return the AppWorks product description as used in the OTDS license.
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
str:
|
|
499
|
+
The AppWorks product description.
|
|
500
|
+
|
|
501
|
+
"""
|
|
502
|
+
|
|
503
|
+
return self.config()["productDescription"]
|
|
504
|
+
|
|
505
|
+
# end method definition
|
|
506
|
+
|
|
507
|
+
def hostname(self) -> str:
|
|
508
|
+
"""Return the AppWorks hostname.
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
str:
|
|
512
|
+
The AppWorks hostname.
|
|
513
|
+
|
|
514
|
+
"""
|
|
515
|
+
|
|
516
|
+
return self.config()["hostname"]
|
|
517
|
+
|
|
518
|
+
def username(self) -> str:
|
|
519
|
+
"""Return the AppWorks username.
|
|
520
|
+
|
|
521
|
+
Returns:
|
|
522
|
+
str:
|
|
523
|
+
The AppWorks username
|
|
524
|
+
|
|
525
|
+
"""
|
|
526
|
+
|
|
527
|
+
return self.config()["username"]
|
|
528
|
+
|
|
529
|
+
# end method definition
|
|
530
|
+
|
|
531
|
+
def password(self) -> str:
|
|
532
|
+
"""Return the AppWorks password.
|
|
533
|
+
|
|
534
|
+
Returns:
|
|
535
|
+
str:
|
|
536
|
+
The AppWorks password.
|
|
537
|
+
|
|
538
|
+
"""
|
|
539
|
+
|
|
540
|
+
return self.config()["password"]
|
|
541
|
+
|
|
542
|
+
# end method definition
|
|
543
|
+
|
|
544
|
+
def config_map_name(self) -> str:
|
|
545
|
+
"""Return AppWorks Kubernetes config map name.
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
str:
|
|
549
|
+
The Kubernetes config map name of AppWorks.
|
|
550
|
+
|
|
146
551
|
"""
|
|
147
|
-
|
|
552
|
+
|
|
553
|
+
return self.config()["configMapName"]
|
|
148
554
|
|
|
149
555
|
# end method definition
|
|
150
556
|
|
|
151
557
|
def config(self) -> dict:
|
|
152
|
-
"""
|
|
558
|
+
"""Return the configuration dictionary.
|
|
559
|
+
|
|
153
560
|
Returns:
|
|
154
561
|
dict: Configuration dictionary
|
|
562
|
+
|
|
155
563
|
"""
|
|
564
|
+
|
|
156
565
|
return self._config
|
|
157
566
|
|
|
158
567
|
# end method definition
|
|
159
568
|
|
|
160
569
|
def cookie(self) -> dict:
|
|
161
|
-
"""
|
|
162
|
-
|
|
570
|
+
"""Return the login cookie of OTAWP.
|
|
571
|
+
|
|
572
|
+
This is set by the authenticate() method
|
|
573
|
+
|
|
163
574
|
Returns:
|
|
164
|
-
dict:
|
|
575
|
+
dict:
|
|
576
|
+
OTAWP cookie
|
|
577
|
+
|
|
165
578
|
"""
|
|
579
|
+
|
|
166
580
|
return self._cookie
|
|
167
581
|
|
|
168
582
|
# end method definition
|
|
169
583
|
|
|
170
584
|
def credentials(self) -> str:
|
|
171
|
-
"""
|
|
585
|
+
"""Return the SOAP payload with credentials (username and password).
|
|
586
|
+
|
|
172
587
|
Returns:
|
|
173
|
-
str:
|
|
588
|
+
str:
|
|
589
|
+
SOAP payload with username and password.
|
|
590
|
+
|
|
174
591
|
"""
|
|
592
|
+
|
|
175
593
|
username = self.config()["username"]
|
|
176
594
|
password = self.config()["password"]
|
|
177
595
|
|
|
@@ -199,146 +617,203 @@ class OTAWP:
|
|
|
199
617
|
</SOAP:Body>
|
|
200
618
|
</SOAP:Envelope>
|
|
201
619
|
"""
|
|
620
|
+
|
|
202
621
|
return soap_payload
|
|
203
622
|
|
|
204
623
|
# end method definition
|
|
205
624
|
|
|
206
625
|
def credential_url(self) -> str:
|
|
207
|
-
"""
|
|
626
|
+
"""Return the credentials URL of OTAWP.
|
|
208
627
|
|
|
209
628
|
Returns:
|
|
210
|
-
str:
|
|
629
|
+
str:
|
|
630
|
+
The AppWorks credentials URL.
|
|
631
|
+
|
|
211
632
|
"""
|
|
633
|
+
|
|
212
634
|
return self.config()["gatewayAuthenticationUrl"]
|
|
213
635
|
|
|
214
636
|
# end method definition
|
|
215
637
|
|
|
216
638
|
def gateway_url(self) -> str:
|
|
217
|
-
"""
|
|
639
|
+
"""Return SOAP gateway URL of OTAWP.
|
|
218
640
|
|
|
219
641
|
Returns:
|
|
220
|
-
str:
|
|
642
|
+
str:
|
|
643
|
+
The AppWorks SOAP gateway URL.
|
|
644
|
+
|
|
221
645
|
"""
|
|
646
|
+
|
|
222
647
|
return self.config()["soapGatewayUrl"]
|
|
223
648
|
|
|
224
649
|
# end method definition
|
|
225
650
|
|
|
226
|
-
def
|
|
227
|
-
"""
|
|
651
|
+
def get_create_priority_url(self) -> str:
|
|
652
|
+
"""Return create priority URL of OTAWP.
|
|
228
653
|
|
|
229
654
|
Returns:
|
|
230
|
-
str:
|
|
655
|
+
str:
|
|
656
|
+
The create priority URL.
|
|
657
|
+
|
|
231
658
|
"""
|
|
232
|
-
|
|
659
|
+
|
|
660
|
+
return self.config()["priorityUrl"] + "?defaultinst_ct=abcd"
|
|
233
661
|
|
|
234
662
|
# end method definition
|
|
235
663
|
|
|
236
|
-
def
|
|
237
|
-
"""
|
|
664
|
+
def get_priorities_list_url(self) -> str:
|
|
665
|
+
"""Get OTAWP URL to retrieve a list of all priorities.
|
|
238
666
|
|
|
239
667
|
Returns:
|
|
240
|
-
str:
|
|
668
|
+
str:
|
|
669
|
+
The AppWorks URL to get a list of all priorities.
|
|
670
|
+
|
|
241
671
|
"""
|
|
242
|
-
|
|
672
|
+
|
|
673
|
+
return self.config()["priorityListUrl"]
|
|
243
674
|
|
|
244
675
|
# end method definition
|
|
245
676
|
|
|
246
|
-
def
|
|
247
|
-
"""
|
|
677
|
+
def get_create_customer_url(self) -> str:
|
|
678
|
+
"""Return create customer URL of OTAWP.
|
|
248
679
|
|
|
249
680
|
Returns:
|
|
250
|
-
str:
|
|
681
|
+
str:
|
|
682
|
+
The create customer URL.
|
|
683
|
+
|
|
251
684
|
"""
|
|
252
|
-
|
|
685
|
+
|
|
686
|
+
return self.config()["customerUrl"] + "?defaultinst_ct=abcd"
|
|
253
687
|
|
|
254
688
|
# end method definition
|
|
255
689
|
|
|
256
|
-
def
|
|
257
|
-
"""
|
|
690
|
+
def get_customers_list_url(self) -> str:
|
|
691
|
+
"""Get OTAWP URL to retrieve a list of all customers.
|
|
258
692
|
|
|
259
693
|
Returns:
|
|
260
|
-
str:
|
|
694
|
+
str:
|
|
695
|
+
The AppWorks URL to get a list of all customers.
|
|
696
|
+
|
|
261
697
|
"""
|
|
262
|
-
|
|
698
|
+
|
|
699
|
+
return self.config()["customerListUrl"]
|
|
263
700
|
|
|
264
701
|
# end method definition
|
|
265
702
|
|
|
266
|
-
def
|
|
267
|
-
"""
|
|
703
|
+
def get_create_casetype_url(self) -> str:
|
|
704
|
+
"""Return create case type URL of OTAWP.
|
|
268
705
|
|
|
269
706
|
Returns:
|
|
270
|
-
str:
|
|
707
|
+
str:
|
|
708
|
+
The create case type URL.
|
|
709
|
+
|
|
271
710
|
"""
|
|
272
|
-
|
|
711
|
+
|
|
712
|
+
return self.config()["caseTypeUrl"] + "?defaultinst_ct=abcd"
|
|
273
713
|
|
|
274
714
|
# end method definition
|
|
275
715
|
|
|
276
|
-
def
|
|
277
|
-
"""
|
|
716
|
+
def get_casetypes_list_url(self) -> str:
|
|
717
|
+
"""Get OTAWP URL to retrieve a list of all case types.
|
|
278
718
|
|
|
279
719
|
Returns:
|
|
280
|
-
str:
|
|
720
|
+
str:
|
|
721
|
+
The get all case types URL.
|
|
722
|
+
|
|
281
723
|
"""
|
|
282
|
-
|
|
724
|
+
|
|
725
|
+
return self.config()["caseTypeListUrl"]
|
|
283
726
|
|
|
284
727
|
# end method definition
|
|
285
728
|
|
|
286
|
-
def
|
|
287
|
-
"""
|
|
729
|
+
def get_create_category_url(self) -> str:
|
|
730
|
+
"""Get OTAWP URL to create a category.
|
|
288
731
|
|
|
289
732
|
Returns:
|
|
290
|
-
str:
|
|
733
|
+
str:
|
|
734
|
+
The create category URL.
|
|
735
|
+
|
|
291
736
|
"""
|
|
292
|
-
|
|
737
|
+
|
|
738
|
+
return self.config()["categoryUrl"] + "?defaultinst_ct=abcd"
|
|
293
739
|
|
|
294
740
|
# end method definition
|
|
295
741
|
|
|
296
|
-
def
|
|
297
|
-
"""
|
|
742
|
+
def get_categories_list_url(self) -> str:
|
|
743
|
+
"""Get OTAWP URL to retrieve a list of all categories.
|
|
298
744
|
|
|
299
745
|
Returns:
|
|
300
|
-
str:
|
|
746
|
+
str:
|
|
747
|
+
The get all categories URL.
|
|
748
|
+
|
|
301
749
|
"""
|
|
302
|
-
|
|
750
|
+
|
|
751
|
+
return self.config()["categoryListUrl"]
|
|
303
752
|
|
|
304
753
|
# end method definition
|
|
305
754
|
|
|
306
|
-
def
|
|
307
|
-
"""
|
|
755
|
+
def get_create_case_url(self) -> str:
|
|
756
|
+
"""Get OTAWP URL to create a case (e.g. a loan).
|
|
308
757
|
|
|
309
758
|
Returns:
|
|
310
|
-
str:
|
|
759
|
+
str:
|
|
760
|
+
The create case URL.
|
|
761
|
+
|
|
311
762
|
"""
|
|
312
|
-
|
|
763
|
+
|
|
764
|
+
return self.config()["caseUrl"] + "?defaultinst_ct=abcd"
|
|
313
765
|
|
|
314
766
|
# end method definition
|
|
315
767
|
|
|
316
|
-
def
|
|
317
|
-
"""
|
|
318
|
-
|
|
768
|
+
def get_cases_list_url(self) -> str:
|
|
769
|
+
"""Return get all loans URL of OTAWP.
|
|
770
|
+
|
|
771
|
+
Returns:
|
|
772
|
+
str:
|
|
773
|
+
The get all loans URL.
|
|
774
|
+
|
|
775
|
+
"""
|
|
776
|
+
|
|
777
|
+
return self.config()["caseListUrl"]
|
|
319
778
|
|
|
320
779
|
# end method definition
|
|
321
780
|
|
|
322
|
-
def parse_xml(self, xml_string):
|
|
323
|
-
"""Parse XML string and return a dictionary without namespaces.
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
781
|
+
def parse_xml(self, xml_string: str) -> dict:
|
|
782
|
+
"""Parse XML string and return a dictionary without namespaces.
|
|
783
|
+
|
|
784
|
+
Args:
|
|
785
|
+
xml_string (str):
|
|
786
|
+
The XML string to process.
|
|
787
|
+
|
|
788
|
+
Returns:
|
|
789
|
+
dict:
|
|
790
|
+
The XML structure converted to a dictionary.
|
|
791
|
+
|
|
792
|
+
"""
|
|
793
|
+
|
|
794
|
+
return XML.xml_to_dict(xml_string=xml_string)
|
|
333
795
|
|
|
334
796
|
# end method definition
|
|
335
797
|
|
|
336
|
-
def find_key(self, data, target_key):
|
|
337
|
-
"""Recursively search for a key in a nested dictionary and return its value.
|
|
798
|
+
def find_key(self, data: dict | list, target_key: str) -> str | None:
|
|
799
|
+
"""Recursively search for a key in a nested dictionary and return its value.
|
|
800
|
+
|
|
801
|
+
Args:
|
|
802
|
+
data (dict | list):
|
|
803
|
+
The data structure to find a key in.
|
|
804
|
+
target_key (str):
|
|
805
|
+
The key to find.
|
|
806
|
+
|
|
807
|
+
Returns:
|
|
808
|
+
str:
|
|
809
|
+
The value for the key.
|
|
810
|
+
|
|
811
|
+
"""
|
|
812
|
+
|
|
338
813
|
if isinstance(data, dict):
|
|
339
814
|
if target_key in data:
|
|
340
815
|
return data[target_key]
|
|
341
|
-
for
|
|
816
|
+
for value in data.values():
|
|
342
817
|
result = self.find_key(value, target_key)
|
|
343
818
|
if result is not None:
|
|
344
819
|
return result
|
|
@@ -347,30 +822,188 @@ class OTAWP:
|
|
|
347
822
|
result = self.find_key(item, target_key)
|
|
348
823
|
if result is not None:
|
|
349
824
|
return result
|
|
825
|
+
|
|
350
826
|
return None
|
|
351
827
|
|
|
352
828
|
# end method definition
|
|
353
829
|
|
|
830
|
+
def get_soap_element(self, soap_response: str, soap_tag: str) -> str | None:
|
|
831
|
+
"""Retrieve an element from the XML SOAP response.
|
|
832
|
+
|
|
833
|
+
Args:
|
|
834
|
+
soap_response (str):
|
|
835
|
+
The unparsed XML string of the SOAP response.
|
|
836
|
+
soap_tag (str):
|
|
837
|
+
The XML tag name (without namespace) of the element
|
|
838
|
+
incuding the text to be returned.
|
|
839
|
+
|
|
840
|
+
Returns:
|
|
841
|
+
str | None:
|
|
842
|
+
SOAP message if found in the SOAP response or NONE otherwise.
|
|
843
|
+
|
|
844
|
+
"""
|
|
845
|
+
|
|
846
|
+
soap_data = self.parse_xml(soap_response)
|
|
847
|
+
soap_string = self.find_key(data=soap_data, target_key=soap_tag)
|
|
848
|
+
|
|
849
|
+
return soap_string
|
|
850
|
+
|
|
851
|
+
# end method definition
|
|
852
|
+
|
|
853
|
+
def do_request(
|
|
854
|
+
self,
|
|
855
|
+
url: str,
|
|
856
|
+
method: str = "GET",
|
|
857
|
+
headers: dict | None = None,
|
|
858
|
+
cookies: dict | None = None,
|
|
859
|
+
data: dict | None = None,
|
|
860
|
+
json_data: dict | None = None,
|
|
861
|
+
files: dict | None = None,
|
|
862
|
+
timeout: int | None = REQUEST_TIMEOUT,
|
|
863
|
+
show_error: bool = True,
|
|
864
|
+
show_warning: bool = False,
|
|
865
|
+
warning_message: str = "",
|
|
866
|
+
failure_message: str = "",
|
|
867
|
+
success_message: str = "",
|
|
868
|
+
parse_request_response: bool = True,
|
|
869
|
+
verify: bool = True,
|
|
870
|
+
) -> dict | None:
|
|
871
|
+
"""Call an AppWorks REST API in a safe way.
|
|
872
|
+
|
|
873
|
+
Args:
|
|
874
|
+
url (str):
|
|
875
|
+
The URL to send the request to.
|
|
876
|
+
method (str, optional):
|
|
877
|
+
HTTP method (GET, POST, etc.). Defaults to "GET".
|
|
878
|
+
headers (dict | None, optional):
|
|
879
|
+
Request Headers. Defaults to None.
|
|
880
|
+
cookies (dict | None, optional):
|
|
881
|
+
Request cookies. Defaults to None.
|
|
882
|
+
data (dict | None, optional):
|
|
883
|
+
Request payload. Defaults to None
|
|
884
|
+
json_data (dict | None, optional):
|
|
885
|
+
Request payload for the JSON parameter. Defaults to None.
|
|
886
|
+
files (dict | None, optional):
|
|
887
|
+
Dictionary of {"name": file-tuple} for multipart encoding upload.
|
|
888
|
+
The file-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
|
|
889
|
+
timeout (int | None, optional):
|
|
890
|
+
Timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
|
|
891
|
+
show_error (bool, optional):
|
|
892
|
+
Whether or not an error should be logged in case of a failed REST call.
|
|
893
|
+
If False, then only a warning is logged. Defaults to True.
|
|
894
|
+
show_warning (bool, optional):
|
|
895
|
+
Whether or not an warning should be logged in case of a
|
|
896
|
+
failed REST call.
|
|
897
|
+
If False, then only a warning is logged. Defaults to True.
|
|
898
|
+
warning_message (str, optional):
|
|
899
|
+
Specific warning message. Defaults to "". If not given the error_message will be used.
|
|
900
|
+
failure_message (str, optional):
|
|
901
|
+
Specific error message. Defaults to "".
|
|
902
|
+
success_message (str, optional):
|
|
903
|
+
Specific success message. Defaults to "".
|
|
904
|
+
parse_request_response (bool, optional):
|
|
905
|
+
If True the response.text will be interpreted as json and loaded into a dictionary.
|
|
906
|
+
True is the default.
|
|
907
|
+
user_credentials (bool, optional):
|
|
908
|
+
Defines if admin or user credentials are used for the REST API call.
|
|
909
|
+
Default = False = admin credentials
|
|
910
|
+
verify (bool, optional):
|
|
911
|
+
Specify whether or not SSL certificates should be verified when making an HTTPS request.
|
|
912
|
+
Default = True
|
|
913
|
+
|
|
914
|
+
Returns:
|
|
915
|
+
dict | None:
|
|
916
|
+
Response of OTDS REST API or None in case of an error.
|
|
917
|
+
|
|
918
|
+
"""
|
|
919
|
+
|
|
920
|
+
# In case of an expired session we reauthenticate and
|
|
921
|
+
# try 1 more time. Session expiration should not happen
|
|
922
|
+
# twice in a row:
|
|
923
|
+
retries = 0
|
|
924
|
+
|
|
925
|
+
while True:
|
|
926
|
+
try:
|
|
927
|
+
response = requests.request(
|
|
928
|
+
method=method,
|
|
929
|
+
url=url,
|
|
930
|
+
data=data,
|
|
931
|
+
json=json_data,
|
|
932
|
+
files=files,
|
|
933
|
+
headers=headers,
|
|
934
|
+
cookies=cookies,
|
|
935
|
+
timeout=timeout,
|
|
936
|
+
verify=verify,
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
except requests.RequestException as req_exception:
|
|
940
|
+
self.logger.error(
|
|
941
|
+
"%s; error -> %s",
|
|
942
|
+
failure_message if failure_message else "Request to -> %s failed",
|
|
943
|
+
str(req_exception),
|
|
944
|
+
)
|
|
945
|
+
return None
|
|
946
|
+
|
|
947
|
+
if response.ok:
|
|
948
|
+
if success_message:
|
|
949
|
+
self.logger.info(success_message)
|
|
950
|
+
if parse_request_response:
|
|
951
|
+
return self.parse_request_response(response_object=response, show_error=show_error)
|
|
952
|
+
else:
|
|
953
|
+
return response
|
|
954
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
955
|
+
elif response.status_code == 401 and retries == 0:
|
|
956
|
+
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
957
|
+
self.authenticate(revalidate=True)
|
|
958
|
+
retries += 1
|
|
959
|
+
continue
|
|
960
|
+
elif show_error:
|
|
961
|
+
self.logger.error(
|
|
962
|
+
"%s; status -> %s/%s; error -> %s",
|
|
963
|
+
failure_message,
|
|
964
|
+
response.status_code,
|
|
965
|
+
HTTPStatus(response.status_code).phrase,
|
|
966
|
+
response.text,
|
|
967
|
+
)
|
|
968
|
+
elif show_warning:
|
|
969
|
+
self.logger.warning(
|
|
970
|
+
"%s; status -> %s/%s; warning -> %s",
|
|
971
|
+
warning_message if warning_message else failure_message,
|
|
972
|
+
response.status_code,
|
|
973
|
+
HTTPStatus(response.status_code).phrase,
|
|
974
|
+
response.text,
|
|
975
|
+
)
|
|
976
|
+
return None
|
|
977
|
+
# end while True
|
|
978
|
+
|
|
979
|
+
# end method definition
|
|
980
|
+
|
|
354
981
|
def parse_request_response(
|
|
355
982
|
self,
|
|
356
983
|
response_object: object,
|
|
357
984
|
additional_error_message: str = "",
|
|
358
985
|
show_error: bool = True,
|
|
359
986
|
) -> dict | None:
|
|
360
|
-
"""
|
|
361
|
-
that also handles exceptions.
|
|
987
|
+
"""Convert the text property of a request response object to a Python dict in a safe way.
|
|
362
988
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
989
|
+
Properly handle exceptions.
|
|
990
|
+
|
|
991
|
+
AppWorks may produce corrupt response when it gets restarted
|
|
992
|
+
or hitting resource limits. So we try to avoid a fatal error and bail
|
|
993
|
+
out more gracefully.
|
|
366
994
|
|
|
367
995
|
Args:
|
|
368
|
-
response_object (object):
|
|
369
|
-
|
|
370
|
-
|
|
996
|
+
response_object (object):
|
|
997
|
+
This is reponse object delivered by the request call.
|
|
998
|
+
additional_error_message (str):
|
|
999
|
+
Print a custom error message.
|
|
1000
|
+
show_error (bool):
|
|
1001
|
+
If True log an error, if False log a warning.
|
|
371
1002
|
|
|
372
1003
|
Returns:
|
|
373
|
-
dict:
|
|
1004
|
+
dict:
|
|
1005
|
+
Response or None in case of an error.
|
|
1006
|
+
|
|
374
1007
|
"""
|
|
375
1008
|
|
|
376
1009
|
if not response_object:
|
|
@@ -381,284 +1014,709 @@ class OTAWP:
|
|
|
381
1014
|
except json.JSONDecodeError as exception:
|
|
382
1015
|
if additional_error_message:
|
|
383
1016
|
message = "Cannot decode response as JSon. {}; error -> {}".format(
|
|
384
|
-
additional_error_message,
|
|
1017
|
+
additional_error_message,
|
|
1018
|
+
exception,
|
|
385
1019
|
)
|
|
386
1020
|
else:
|
|
387
1021
|
message = "Cannot decode response as JSon; error -> {}".format(
|
|
388
|
-
exception
|
|
1022
|
+
exception,
|
|
389
1023
|
)
|
|
390
1024
|
if show_error:
|
|
391
|
-
logger.error(message)
|
|
1025
|
+
self.logger.error(message)
|
|
392
1026
|
else:
|
|
393
|
-
logger.warning(message)
|
|
1027
|
+
self.logger.warning(message)
|
|
394
1028
|
return None
|
|
1029
|
+
|
|
395
1030
|
return dict_object
|
|
396
1031
|
|
|
397
1032
|
# end method definition
|
|
398
1033
|
|
|
399
|
-
def
|
|
400
|
-
"""
|
|
1034
|
+
def get_entity_value(self, entity: dict, key: str, show_error: bool = True) -> str | int | None:
|
|
1035
|
+
"""Read an entity value from the REST API response.
|
|
401
1036
|
|
|
402
1037
|
Args:
|
|
403
|
-
|
|
404
|
-
|
|
1038
|
+
entity (dict):
|
|
1039
|
+
An entity - typically consisting of a dictionary with a "_links" and "Properties" keys. Example:
|
|
1040
|
+
{
|
|
1041
|
+
'_links': {
|
|
1042
|
+
'item': {...}
|
|
1043
|
+
},
|
|
1044
|
+
'Properties': {
|
|
1045
|
+
'Name': 'Test 1',
|
|
1046
|
+
'Description': 'Test 1 Description',
|
|
1047
|
+
'CasePrefix': 'TEST',
|
|
1048
|
+
'Status': 1
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
key (str):
|
|
1052
|
+
Key to find (e.g., "id", "name"). For key "id" there's a special
|
|
1053
|
+
handling as the ID is only provided in the 'href' in the '_links'
|
|
1054
|
+
sub-dictionary.
|
|
1055
|
+
show_error (bool, optional):
|
|
1056
|
+
Whether an error or just a warning should be logged.
|
|
1057
|
+
|
|
405
1058
|
Returns:
|
|
406
|
-
|
|
1059
|
+
str | None:
|
|
1060
|
+
Value of the entity property with the given key, or None if no value is found.
|
|
1061
|
+
|
|
407
1062
|
"""
|
|
408
1063
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
logger.info(
|
|
412
|
-
"Session still valid - return existing cookie -> %s",
|
|
413
|
-
str(self._cookie),
|
|
414
|
-
)
|
|
415
|
-
return self._cookie
|
|
1064
|
+
if not entity or "Properties" not in entity:
|
|
1065
|
+
return None
|
|
416
1066
|
|
|
417
|
-
|
|
1067
|
+
properties = entity["Properties"]
|
|
418
1068
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
response = requests.post(
|
|
423
|
-
url=self.credential_url(),
|
|
424
|
-
data=self.credentials(),
|
|
425
|
-
headers=REQUEST_HEADERS,
|
|
426
|
-
timeout=REQUEST_TIMEOUT
|
|
427
|
-
)
|
|
428
|
-
except requests.exceptions.RequestException as exception:
|
|
429
|
-
logger.warning(
|
|
430
|
-
"Unable to connect to -> %s; error -> %s",
|
|
431
|
-
self.credential_url(),
|
|
432
|
-
exception.strerror,
|
|
433
|
-
)
|
|
434
|
-
logger.warning("OTAWP service may not be ready yet.")
|
|
1069
|
+
if key not in properties and key != "id":
|
|
1070
|
+
if show_error:
|
|
1071
|
+
self.logger.error("Key -> '%s' not found in entity -> '%s'!", key, str(entity))
|
|
435
1072
|
return None
|
|
436
1073
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
if
|
|
1074
|
+
# special handling of IDs which we extract from the self href:
|
|
1075
|
+
if key == "id" and "_links" in entity:
|
|
1076
|
+
links = entity["_links"]
|
|
1077
|
+
if "item" in links:
|
|
1078
|
+
links = links["item"]
|
|
1079
|
+
self_link = links.get("href")
|
|
1080
|
+
match = re.search(r"/(\d+)(?=[^/]*$)", self_link)
|
|
1081
|
+
if not match:
|
|
441
1082
|
return None
|
|
442
|
-
|
|
443
|
-
authenticate_dict, "AssertionArtifact"
|
|
444
|
-
)
|
|
445
|
-
if isinstance(assertion_artifact_dict, dict):
|
|
446
|
-
otawp_ticket = assertion_artifact_dict.get("AssertionArtifact")
|
|
447
|
-
logger.info("SAML token -> %s", otawp_ticket)
|
|
448
|
-
else:
|
|
449
|
-
logger.error("Failed to request an OTAWP ticket; error -> %s", response.text)
|
|
450
|
-
return None
|
|
1083
|
+
return int(match.group(1))
|
|
451
1084
|
|
|
452
|
-
|
|
453
|
-
self._otawp_ticket = otawp_ticket
|
|
454
|
-
|
|
455
|
-
return self._cookie
|
|
1085
|
+
return properties[key]
|
|
456
1086
|
|
|
457
1087
|
# end method definition
|
|
458
1088
|
|
|
459
|
-
def
|
|
1089
|
+
def get_result_value(
|
|
460
1090
|
self,
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
1091
|
+
response: dict,
|
|
1092
|
+
entity_type: str,
|
|
1093
|
+
key: str,
|
|
1094
|
+
index: int = 0,
|
|
1095
|
+
show_error: bool = True,
|
|
1096
|
+
) -> str | int | None:
|
|
1097
|
+
"""Read an item value from the REST API response.
|
|
1098
|
+
|
|
465
1099
|
Args:
|
|
466
|
-
|
|
467
|
-
|
|
1100
|
+
response (dict):
|
|
1101
|
+
REST API response object.
|
|
1102
|
+
entity_type (str):
|
|
1103
|
+
Name of the sub-dictionary holding the actual values.
|
|
1104
|
+
This typically stands for the type of the AppWorks entity.
|
|
1105
|
+
key (str):
|
|
1106
|
+
Key to find (e.g., "id", "name").
|
|
1107
|
+
index (int, optional):
|
|
1108
|
+
Index to use if a list of results is delivered (1st element has index 0).
|
|
1109
|
+
Defaults to 0.
|
|
1110
|
+
show_error (bool, optional):
|
|
1111
|
+
Whether an error or just a warning should be logged.
|
|
1112
|
+
|
|
468
1113
|
Returns:
|
|
469
|
-
|
|
1114
|
+
str | None:
|
|
1115
|
+
Value of the item with the given key, or None if no value is found.
|
|
1116
|
+
|
|
470
1117
|
"""
|
|
471
1118
|
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
1119
|
+
if not response:
|
|
1120
|
+
return None
|
|
1121
|
+
|
|
1122
|
+
if "_embedded" not in response:
|
|
1123
|
+
return None
|
|
1124
|
+
|
|
1125
|
+
embedded_data = response["_embedded"]
|
|
1126
|
+
|
|
1127
|
+
if entity_type not in embedded_data:
|
|
1128
|
+
if show_error:
|
|
1129
|
+
self.logger.error("Entity type -> '%s' is not included in response!", entity_type)
|
|
1130
|
+
return None
|
|
1131
|
+
|
|
1132
|
+
entity_list = embedded_data[entity_type]
|
|
1133
|
+
|
|
1134
|
+
try:
|
|
1135
|
+
entity = entity_list[index]
|
|
1136
|
+
except KeyError:
|
|
1137
|
+
if show_error:
|
|
1138
|
+
self.logger.error("Response does not have an entity at index -> %d", index)
|
|
1139
|
+
return None
|
|
1140
|
+
|
|
1141
|
+
return self.get_entity_value(entity=entity, key=key, show_error=show_error)
|
|
1142
|
+
|
|
1143
|
+
# end method definition
|
|
1144
|
+
|
|
1145
|
+
def get_result_values(
|
|
1146
|
+
self,
|
|
1147
|
+
response: dict,
|
|
1148
|
+
entity_type: str,
|
|
1149
|
+
key: str,
|
|
1150
|
+
show_error: bool = True,
|
|
1151
|
+
) -> list | None:
|
|
1152
|
+
"""Read an values from the REST API response.
|
|
1153
|
+
|
|
1154
|
+
Args:
|
|
1155
|
+
response (dict):
|
|
1156
|
+
REST API response object.
|
|
1157
|
+
entity_type (str):
|
|
1158
|
+
Name of the sub-dictionary holding the actual values.
|
|
1159
|
+
This typically stands for the type of the AppWorks entity.
|
|
1160
|
+
key (str):
|
|
1161
|
+
Key to find (e.g., "id", "name").
|
|
1162
|
+
show_error (bool, optional):
|
|
1163
|
+
Whether an error or just a warning should be logged.
|
|
1164
|
+
|
|
1165
|
+
Returns:
|
|
1166
|
+
list | None:
|
|
1167
|
+
Values of the items with the given key, or [] if the list
|
|
1168
|
+
of values is empty, or None if the response is not in the
|
|
1169
|
+
expected format.
|
|
1170
|
+
|
|
1171
|
+
"""
|
|
1172
|
+
|
|
1173
|
+
results = []
|
|
1174
|
+
|
|
1175
|
+
if not response:
|
|
1176
|
+
return None
|
|
1177
|
+
|
|
1178
|
+
if "_embedded" not in response:
|
|
1179
|
+
return None
|
|
1180
|
+
|
|
1181
|
+
embedded_data = response["_embedded"]
|
|
1182
|
+
|
|
1183
|
+
if entity_type not in embedded_data:
|
|
1184
|
+
if show_error:
|
|
1185
|
+
self.logger.error("Entity type -> '%s' is not included in response!", entity_type)
|
|
1186
|
+
return None
|
|
1187
|
+
|
|
1188
|
+
entity_list = embedded_data[entity_type]
|
|
1189
|
+
|
|
1190
|
+
for entity in entity_list or []:
|
|
1191
|
+
entity_value = self.get_entity_value(entity=entity, key=key, show_error=show_error)
|
|
1192
|
+
if entity_value:
|
|
1193
|
+
results.append(entity_value)
|
|
1194
|
+
|
|
1195
|
+
return results
|
|
1196
|
+
|
|
1197
|
+
# end method definition
|
|
1198
|
+
|
|
1199
|
+
def get_result_item(
|
|
1200
|
+
self,
|
|
1201
|
+
response: dict,
|
|
1202
|
+
entity_type: str,
|
|
1203
|
+
key: str,
|
|
1204
|
+
value: str,
|
|
1205
|
+
show_error: bool = True,
|
|
1206
|
+
) -> dict | None:
|
|
1207
|
+
"""Check existence of key / value pair in the response properties of an REST API call.
|
|
1208
|
+
|
|
1209
|
+
Args:
|
|
1210
|
+
response (dict):
|
|
1211
|
+
REST response from an AppWorks REST Call.
|
|
1212
|
+
Name of the sub-dictionary holding the actual values.
|
|
1213
|
+
This typically stands for the type of the AppWorks entity.
|
|
1214
|
+
entity_type (str):
|
|
1215
|
+
Name of the sub-dictionary holding the actual values.
|
|
1216
|
+
This typically stands for the type of the AppWorks entity.
|
|
1217
|
+
key (str):
|
|
1218
|
+
The property name (key).
|
|
1219
|
+
value (str):
|
|
1220
|
+
The value to find in the item with the matching key.
|
|
1221
|
+
show_error (bool, optional):
|
|
1222
|
+
Whether an error or just a warning should be logged.
|
|
1223
|
+
|
|
1224
|
+
Returns:
|
|
1225
|
+
dict | None:
|
|
1226
|
+
Entity data or None in case entity with key/value was not found.
|
|
1227
|
+
|
|
1228
|
+
"""
|
|
1229
|
+
|
|
1230
|
+
if not response:
|
|
1231
|
+
return None
|
|
1232
|
+
|
|
1233
|
+
if "_embedded" not in response:
|
|
1234
|
+
return None
|
|
1235
|
+
|
|
1236
|
+
embedded_data = response["_embedded"]
|
|
1237
|
+
|
|
1238
|
+
if entity_type not in embedded_data:
|
|
1239
|
+
if show_error:
|
|
1240
|
+
self.logger.error("Entity type -> '%s' is not included in response!", entity_type)
|
|
1241
|
+
return None
|
|
1242
|
+
|
|
1243
|
+
entity_list = embedded_data[entity_type]
|
|
1244
|
+
|
|
1245
|
+
for entity in entity_list:
|
|
1246
|
+
if "Properties" not in entity:
|
|
1247
|
+
continue
|
|
1248
|
+
|
|
1249
|
+
properties = entity["Properties"]
|
|
1250
|
+
|
|
1251
|
+
if key not in properties:
|
|
1252
|
+
if show_error:
|
|
1253
|
+
self.logger.error("Key -> '%s' is not in properties of entity -> '%s'!", key, str(entity))
|
|
1254
|
+
continue
|
|
1255
|
+
if properties[key] == value:
|
|
1256
|
+
return entity
|
|
1257
|
+
|
|
1258
|
+
return None
|
|
1259
|
+
|
|
1260
|
+
# end method definition
|
|
1261
|
+
|
|
1262
|
+
def authenticate(self, revalidate: bool = False) -> dict | None:
|
|
1263
|
+
"""Authenticate at AppWorks.
|
|
1264
|
+
|
|
1265
|
+
Args:
|
|
1266
|
+
revalidate (bool, optional):
|
|
1267
|
+
Determine if a re-authentication is enforced
|
|
1268
|
+
(e.g. if session has timed out with 401 error).
|
|
1269
|
+
|
|
1270
|
+
Returns:
|
|
1271
|
+
dict | None:
|
|
1272
|
+
Cookie information. Also stores cookie information in self._cookie.
|
|
1273
|
+
None in case of an error.
|
|
1274
|
+
|
|
1275
|
+
Example:
|
|
1276
|
+
{
|
|
1277
|
+
'defaultinst_SAMLart': 'e0pBVkEtQUVTL0...tj5m6w==',
|
|
1278
|
+
'defaultinst_ct': 'abcd'
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
"""
|
|
1282
|
+
|
|
1283
|
+
self.logger.info("Authenticate at AppWorks organization -> '%s'...", self.config()["organization"])
|
|
1284
|
+
|
|
1285
|
+
if self._cookie and not revalidate:
|
|
1286
|
+
self.logger.debug(
|
|
1287
|
+
"Session still valid - return existing cookie -> %s",
|
|
1288
|
+
str(self._cookie),
|
|
1289
|
+
)
|
|
1290
|
+
return self._cookie
|
|
1291
|
+
|
|
1292
|
+
otawp_ticket = "NotSet"
|
|
1293
|
+
|
|
1294
|
+
request_url = self.credential_url()
|
|
1295
|
+
|
|
1296
|
+
retries = 0
|
|
1297
|
+
response = None # seems to be necessary here
|
|
1298
|
+
|
|
1299
|
+
while retries < REQUEST_MAX_RETRIES:
|
|
1300
|
+
try:
|
|
1301
|
+
response = requests.post(
|
|
1302
|
+
url=request_url,
|
|
1303
|
+
data=self.credentials(),
|
|
1304
|
+
headers=REQUEST_HEADERS_XML,
|
|
1305
|
+
timeout=REQUEST_TIMEOUT,
|
|
1306
|
+
)
|
|
1307
|
+
except requests.exceptions.RequestException as exception:
|
|
1308
|
+
self.logger.warning(
|
|
1309
|
+
"Unable to connect to OTAWP authentication endpoint -> %s; error -> %s",
|
|
1310
|
+
self.credential_url(),
|
|
1311
|
+
str(exception),
|
|
1312
|
+
)
|
|
1313
|
+
self.logger.warning("OTAWP service may not be ready yet. Retry in %d seconds...", REQUEST_RETRY_DELAY)
|
|
1314
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
1315
|
+
retries += 1
|
|
1316
|
+
continue
|
|
1317
|
+
|
|
1318
|
+
if response.ok:
|
|
1319
|
+
soap_response = self.parse_xml(xml_string=response.text)
|
|
1320
|
+
if not soap_response:
|
|
1321
|
+
self.logger.error("Failed to parse the SOAP response with the authentication data!")
|
|
1322
|
+
self.logger.debug("SOAP message -> %s", response.text)
|
|
1323
|
+
return None
|
|
1324
|
+
otawp_ticket = self.find_key(
|
|
1325
|
+
data=soap_response,
|
|
1326
|
+
target_key="AssertionArtifact",
|
|
1327
|
+
)
|
|
1328
|
+
if otawp_ticket:
|
|
1329
|
+
self.logger.info(
|
|
1330
|
+
"Successfully authenticated at AppWorks organization -> '%s' with URL -> %s and user -> '%s'.",
|
|
1331
|
+
self.config()["organization"],
|
|
1332
|
+
self.credential_url(),
|
|
1333
|
+
self.config()["username"],
|
|
1334
|
+
)
|
|
1335
|
+
self.logger.debug("SAML token -> %s", otawp_ticket)
|
|
1336
|
+
self._cookie = {"defaultinst_SAMLart": otawp_ticket, "defaultinst_ct": "abcd"}
|
|
1337
|
+
self._otawp_ticket = otawp_ticket
|
|
1338
|
+
|
|
1339
|
+
return self._cookie
|
|
1340
|
+
else:
|
|
1341
|
+
self.logger.error(
|
|
1342
|
+
"Cannot retrieve OTAWP ticket! Received corrupt authentication data -> %s",
|
|
1343
|
+
response.text,
|
|
1344
|
+
)
|
|
1345
|
+
return None
|
|
1346
|
+
else:
|
|
1347
|
+
self.logger.error(
|
|
1348
|
+
"Failed to request an OTAWP ticket at authentication URL -> %s with user -> '%s'!%s",
|
|
1349
|
+
self.credential_url(),
|
|
1350
|
+
self.config()["username"],
|
|
1351
|
+
" Reason -> '{}'".format(response.reason) if response.reason else "",
|
|
1352
|
+
)
|
|
1353
|
+
return None
|
|
1354
|
+
|
|
1355
|
+
self.logger.error(
|
|
1356
|
+
"Authentication at AppWorks platform failed after %d retries. %sBailing out.",
|
|
1357
|
+
REQUEST_MAX_RETRIES,
|
|
1358
|
+
"{}. ".format(response.text) if response and response.text else "",
|
|
1359
|
+
)
|
|
1360
|
+
return None
|
|
1361
|
+
|
|
1362
|
+
# end method definition
|
|
1363
|
+
|
|
1364
|
+
def create_workspace(
|
|
1365
|
+
self, workspace_name: str, workspace_id: str, show_error: bool = True
|
|
1366
|
+
) -> tuple[dict | None, bool]:
|
|
1367
|
+
"""Create a workspace in cws.
|
|
1368
|
+
|
|
1369
|
+
Args:
|
|
1370
|
+
workspace_name (str):
|
|
1371
|
+
The name of the workspace.
|
|
1372
|
+
workspace_id (str):
|
|
1373
|
+
The ID of the workspace.
|
|
1374
|
+
show_error (bool, optional):
|
|
1375
|
+
Whether to show an error or a warning instead.
|
|
1376
|
+
|
|
1377
|
+
Returns:
|
|
1378
|
+
dict | None:
|
|
1379
|
+
Response dictionary or error text.
|
|
1380
|
+
bool:
|
|
1381
|
+
True, if a new workspace has been created, False if the workspace did already exist.
|
|
1382
|
+
|
|
1383
|
+
"""
|
|
1384
|
+
|
|
1385
|
+
self.logger.info(
|
|
1386
|
+
"Create workspace -> '%s' (%s)...",
|
|
1387
|
+
workspace_name,
|
|
475
1388
|
workspace_id,
|
|
476
1389
|
)
|
|
1390
|
+
|
|
477
1391
|
unique_id = uuid.uuid4()
|
|
478
1392
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
</SOAP:
|
|
1393
|
+
create_workspace_data = f"""
|
|
1394
|
+
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
1395
|
+
<SOAP:Body>
|
|
1396
|
+
<createWorkspace xmlns="http://schemas.cordys.com/cws/runtime/types/workspace/creation/DevelopmentWorkspaceCreator/1.0" async="false" workspaceID="__CWS System__" xmlns:c="http://schemas.cordys.com/cws/1.0">
|
|
1397
|
+
<instance>
|
|
1398
|
+
<c:Document s="T" path="D43B04C1-CD0B-A1EB-A898-53C71DB5D652">
|
|
1399
|
+
<c:Header>
|
|
1400
|
+
<c:System>
|
|
1401
|
+
<c:TypeID>001A6B1E-0C0C-11DF-F5E9-866B84E5D671</c:TypeID>
|
|
1402
|
+
<c:ID>D43B04C1-CD0B-A1EB-A898-53C71DB5D652</c:ID>
|
|
1403
|
+
<c:Name>D43B04C1-CD0B-A1EB-A898-53C71DB5D652</c:Name>
|
|
1404
|
+
<c:Description>D43B04C1-CD0B-A1EB-A898-53C71DB5D652</c:Description>
|
|
1405
|
+
</c:System>
|
|
1406
|
+
</c:Header>
|
|
1407
|
+
<c:Content>
|
|
1408
|
+
<DevelopmentWorkspaceCreator type="com.cordys.cws.runtime.types.workspace.creation.DevelopmentWorkspaceCreator" runtimeDocumentID="D43B04C1-CD0B-A1EB-A898-53C71DB61652">
|
|
1409
|
+
<Workspace>
|
|
1410
|
+
<uri id="{workspace_id}"/>
|
|
1411
|
+
</Workspace>
|
|
1412
|
+
</DevelopmentWorkspaceCreator>
|
|
1413
|
+
</c:Content>
|
|
1414
|
+
</c:Document>
|
|
1415
|
+
</instance>
|
|
1416
|
+
<__prefetch>
|
|
1417
|
+
<Document xmlns="http://schemas.cordys.com/cws/1.0" path="{workspace_name}" s="N" isLocal="IN_LOCAL">
|
|
1418
|
+
<Header>
|
|
1419
|
+
<System>
|
|
1420
|
+
<ID>{workspace_id}</ID>
|
|
1421
|
+
<Name>{workspace_name}</Name>
|
|
1422
|
+
<TypeID>{{4CE11E00-2D97-45C0-BC6C-FAEC1D871026}}</TypeID>
|
|
1423
|
+
<ParentID/>
|
|
1424
|
+
<Description>{workspace_name}</Description>
|
|
1425
|
+
<CreatedBy>sysadmin</CreatedBy>
|
|
1426
|
+
<CreationDate/>
|
|
1427
|
+
<LastModifiedBy>sysadmin</LastModifiedBy>
|
|
1428
|
+
<LastModifiedDate>2021-04-21T06:52:34.254</LastModifiedDate>
|
|
1429
|
+
<FQN/>
|
|
1430
|
+
<Annotation/>
|
|
1431
|
+
<ParentID/>
|
|
1432
|
+
<OptimisticLock/>
|
|
1433
|
+
</System>
|
|
1434
|
+
</Header>
|
|
1435
|
+
<Content>
|
|
1436
|
+
<DevelopmentWorkspace xmlns="http://schemas.cordys.com/cws/runtime/types/workspace/DevelopmentWorkspace/1.0" runtimeDocumentID="D43B04C1-CD0B-A1EB-A898-53C71DB59652" type="com.cordys.cws.runtime.types.workspace.DevelopmentWorkspace">
|
|
1437
|
+
<ExternalID/>
|
|
1438
|
+
<OrganizationName/>
|
|
1439
|
+
<SCMAdapter>
|
|
1440
|
+
<uri id="{unique_id}"/>
|
|
1441
|
+
</SCMAdapter>
|
|
1442
|
+
<UpgradedTo/>
|
|
1443
|
+
<LastWorkspaceUpgradeStep/>
|
|
1444
|
+
<Metaspace/>
|
|
1445
|
+
</DevelopmentWorkspace>
|
|
1446
|
+
</Content>
|
|
1447
|
+
</Document>
|
|
1448
|
+
<Document xmlns="http://schemas.cordys.com/cws/1.0" path="{workspace_name}/Untitled No SCM adapter" s="N" isLocal="IN_LOCAL">
|
|
1449
|
+
<Header>
|
|
1450
|
+
<System>
|
|
1451
|
+
<ID>{unique_id}</ID>
|
|
1452
|
+
<Name>Untitled No SCM adapter</Name>
|
|
1453
|
+
<TypeID>{{E89F3F62-8CA3-4F93-95A8-F76642FD5124}}</TypeID>
|
|
1454
|
+
<ParentID>{workspace_id}</ParentID>
|
|
1455
|
+
<Description>Untitled No SCM adapter</Description>
|
|
1456
|
+
<CreatedBy>sysadmin</CreatedBy>
|
|
1457
|
+
<CreationDate/>
|
|
1458
|
+
<LastModifiedBy>sysadmin</LastModifiedBy>
|
|
1459
|
+
<LastModifiedDate>2021-04-21T06:52:34.254</LastModifiedDate>
|
|
1460
|
+
<FQN/>
|
|
1461
|
+
<Annotation/>
|
|
1462
|
+
<OptimisticLock/>
|
|
1463
|
+
</System>
|
|
1464
|
+
</Header>
|
|
1465
|
+
<Content>
|
|
1466
|
+
<NullAdapter xmlns="http://schemas.cordys.com/cws/runtime/types/teamdevelopment/NullAdapter/1.0" runtimeDocumentID="D43B04C1-CD0B-A1EB-A898-53C71DB51652" type="com.cordys.cws.runtime.types.teamdevelopment.NullAdapter">
|
|
1467
|
+
<Workspace>
|
|
1468
|
+
<uri id="{workspace_id}"/>
|
|
1469
|
+
</Workspace>
|
|
1470
|
+
</NullAdapter>
|
|
1471
|
+
</Content>
|
|
1472
|
+
</Document>
|
|
1473
|
+
</__prefetch>
|
|
1474
|
+
</createWorkspace>
|
|
1475
|
+
</SOAP:Body>
|
|
1476
|
+
</SOAP:Envelope>"""
|
|
1477
|
+
|
|
1478
|
+
error_messages = [
|
|
1479
|
+
"Collaborative Workspace Service Container is not able to handle the SOAP request",
|
|
1480
|
+
"Service Group Lookup failure",
|
|
1481
|
+
]
|
|
1482
|
+
|
|
1483
|
+
exist_messages = [
|
|
1484
|
+
"Object already exists",
|
|
1485
|
+
"createWorkspaceResponse",
|
|
1486
|
+
]
|
|
1487
|
+
|
|
1488
|
+
request_url = self.gateway_url()
|
|
562
1489
|
|
|
563
1490
|
retries = 0
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
1491
|
+
|
|
1492
|
+
while retries < REQUEST_MAX_RETRIES:
|
|
1493
|
+
try:
|
|
1494
|
+
response = requests.post(
|
|
1495
|
+
url=request_url,
|
|
1496
|
+
data=create_workspace_data,
|
|
1497
|
+
headers=REQUEST_HEADERS_XML,
|
|
1498
|
+
cookies=self.cookie(),
|
|
1499
|
+
timeout=REQUEST_TIMEOUT,
|
|
1500
|
+
)
|
|
1501
|
+
except requests.RequestException as req_exception:
|
|
1502
|
+
self.logger.warning(
|
|
1503
|
+
"Request to create workspace -> '%s' failed with error -> %s. Retry in %d seconds...",
|
|
1504
|
+
workspace_name,
|
|
1505
|
+
str(req_exception),
|
|
1506
|
+
REQUEST_RETRY_DELAY,
|
|
1507
|
+
)
|
|
1508
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
1509
|
+
retries += 1
|
|
1510
|
+
continue
|
|
1511
|
+
|
|
572
1512
|
if response.ok:
|
|
573
|
-
logger.info(
|
|
574
|
-
"Successfully created workspace -> '%s'
|
|
1513
|
+
self.logger.info(
|
|
1514
|
+
"Successfully created workspace -> '%s' (%s).",
|
|
575
1515
|
workspace_name,
|
|
576
1516
|
workspace_id,
|
|
577
1517
|
)
|
|
578
|
-
|
|
1518
|
+
# True indicates that a new workspaces has been created.
|
|
1519
|
+
return (self.parse_xml(response.text), True)
|
|
1520
|
+
|
|
579
1521
|
# Check if Session has expired - then re-authenticate and try once more
|
|
580
1522
|
if response.status_code == 401 and retries == 0:
|
|
581
|
-
logger.warning("Session
|
|
1523
|
+
self.logger.warning("Session expired. Re-authenticating...")
|
|
582
1524
|
self.authenticate(revalidate=True)
|
|
583
1525
|
retries += 1
|
|
584
|
-
|
|
585
|
-
|
|
1526
|
+
continue
|
|
1527
|
+
|
|
1528
|
+
# Check if the workspace does exist already:
|
|
1529
|
+
if any(exist_message in response.text for exist_message in exist_messages):
|
|
1530
|
+
self.logger.info("Workspace -> '%s' with ID -> '%s' already exists!", workspace_name, workspace_id)
|
|
1531
|
+
self.logger.debug("SOAP message -> %s", response.text)
|
|
1532
|
+
|
|
1533
|
+
# False indicates that a new workspaces has NOT been created.
|
|
1534
|
+
return (self.parse_xml(response.text), False)
|
|
1535
|
+
|
|
1536
|
+
# Check if any error message is in the response:
|
|
1537
|
+
if any(error_message in response.text for error_message in error_messages):
|
|
1538
|
+
self.logger.warning(
|
|
1539
|
+
"Workspace service error, waiting %d seconds to retry... (Retry %d of %d)",
|
|
1540
|
+
REQUEST_RETRY_DELAY,
|
|
1541
|
+
retries + 1,
|
|
1542
|
+
REQUEST_MAX_RETRIES,
|
|
1543
|
+
)
|
|
1544
|
+
self.logger.debug("SOAP message -> %s", response.text)
|
|
1545
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
1546
|
+
retries += 1
|
|
1547
|
+
|
|
1548
|
+
# end while retries < REQUEST_MAX_RETRIES:
|
|
1549
|
+
|
|
1550
|
+
# After max retries, log and return the response or handle as needed
|
|
1551
|
+
if show_error:
|
|
1552
|
+
self.logger.error(
|
|
1553
|
+
"Max retries reached for workspace -> '%s', unable to create workspace.",
|
|
1554
|
+
workspace_name,
|
|
1555
|
+
)
|
|
1556
|
+
else:
|
|
1557
|
+
self.logger.warning(
|
|
1558
|
+
"Max retries reached for workspace -> '%s', unable to create workspace.",
|
|
1559
|
+
workspace_name,
|
|
1560
|
+
)
|
|
1561
|
+
|
|
1562
|
+
return (None, False)
|
|
586
1563
|
|
|
587
1564
|
# end method definition
|
|
588
1565
|
|
|
589
|
-
def sync_workspace(
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
workspace_id: str
|
|
593
|
-
) -> dict | None:
|
|
594
|
-
""" sync workspaces
|
|
1566
|
+
def sync_workspace(self, workspace_name: str, workspace_id: str) -> dict | None:
|
|
1567
|
+
"""Synchronize workspace.
|
|
1568
|
+
|
|
595
1569
|
Args:
|
|
596
|
-
workspace_name (str):
|
|
597
|
-
|
|
1570
|
+
workspace_name (str):
|
|
1571
|
+
The name of the workspace.
|
|
1572
|
+
workspace_id (str):
|
|
1573
|
+
The ID of the workspace.
|
|
1574
|
+
|
|
598
1575
|
Returns:
|
|
599
|
-
|
|
1576
|
+
dict | None:
|
|
1577
|
+
Parsed response as a dictionary if successful, None otherwise.
|
|
1578
|
+
|
|
600
1579
|
"""
|
|
601
1580
|
|
|
602
|
-
|
|
1581
|
+
if not workspace_id:
|
|
1582
|
+
self.logger.error(
|
|
1583
|
+
"Cannot synchronize workspace%s without a workspace ID!",
|
|
1584
|
+
" -> '{}'".format(workspace_name) if workspace_name else "",
|
|
1585
|
+
)
|
|
1586
|
+
return None
|
|
603
1587
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
1588
|
+
self.logger.info("Starting synchronization of workspace -> '%s' (%s)...", workspace_name, workspace_id)
|
|
1589
|
+
|
|
1590
|
+
# SOAP request body
|
|
1591
|
+
sync_workspace_data = f"""
|
|
1592
|
+
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
1593
|
+
<SOAP:Body>
|
|
1594
|
+
<Synchronize workspaceID="{workspace_id}" xmlns="http://schemas.cordys.com/cws/synchronize/1.0">
|
|
1595
|
+
<DocumentID/>
|
|
1596
|
+
<Asynchronous>false</Asynchronous>
|
|
1597
|
+
</Synchronize>
|
|
1598
|
+
</SOAP:Body>
|
|
1599
|
+
</SOAP:Envelope>
|
|
1600
|
+
"""
|
|
1601
|
+
|
|
1602
|
+
request_url = self.gateway_url()
|
|
1603
|
+
|
|
1604
|
+
self.logger.debug(
|
|
1605
|
+
"Synchronize workspace -> '%s' (%s); calling -> '%s'",
|
|
1606
|
+
workspace_name,
|
|
1607
|
+
workspace_id,
|
|
1608
|
+
request_url,
|
|
1609
|
+
)
|
|
613
1610
|
|
|
614
1611
|
retries = 0
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
1612
|
+
|
|
1613
|
+
while retries < REQUEST_MAX_RETRIES:
|
|
1614
|
+
try:
|
|
1615
|
+
response = requests.post(
|
|
1616
|
+
url=request_url,
|
|
1617
|
+
data=sync_workspace_data,
|
|
1618
|
+
headers=REQUEST_HEADERS_XML,
|
|
1619
|
+
cookies=self.cookie(),
|
|
1620
|
+
timeout=SYNC_PUBLISH_REQUEST_TIMEOUT,
|
|
1621
|
+
)
|
|
1622
|
+
except requests.RequestException as req_exception:
|
|
1623
|
+
self.logger.warning(
|
|
1624
|
+
"Request to synchronize workspace -> '%s' failed with error -> %s. Retry in %d seconds...",
|
|
1625
|
+
workspace_name,
|
|
1626
|
+
str(req_exception),
|
|
1627
|
+
REQUEST_RETRY_DELAY,
|
|
1628
|
+
)
|
|
1629
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
1630
|
+
retries += 1
|
|
1631
|
+
continue
|
|
1632
|
+
|
|
623
1633
|
if response.ok:
|
|
624
|
-
logger.info("
|
|
1634
|
+
self.logger.info("Successfully synchronized workspace -> '%s' (%s).", workspace_name, workspace_id)
|
|
625
1635
|
return self.parse_xml(response.text)
|
|
1636
|
+
|
|
1637
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
626
1638
|
if response.status_code == 401 and retries == 0:
|
|
627
|
-
logger.warning("Session
|
|
1639
|
+
self.logger.warning("Session expired. Re-authenticating...")
|
|
628
1640
|
self.authenticate(revalidate=True)
|
|
629
1641
|
retries += 1
|
|
630
|
-
|
|
631
|
-
|
|
1642
|
+
continue
|
|
1643
|
+
|
|
1644
|
+
if SOAP_FAULT_INDICATOR in response.text:
|
|
1645
|
+
self.logger.error(
|
|
1646
|
+
"Workspace synchronization failed with error -> '%s' when calling -> %s!",
|
|
1647
|
+
self.get_soap_element(soap_response=response.text, soap_tag="faultstring"),
|
|
1648
|
+
self.get_soap_element(soap_response=response.text, soap_tag="faultactor"),
|
|
1649
|
+
)
|
|
1650
|
+
self.logger.debug("SOAP message -> %s", response.text)
|
|
1651
|
+
return None
|
|
1652
|
+
|
|
1653
|
+
self.logger.error("Unexpected error during workspace synchronization -> %s", response.text)
|
|
1654
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
1655
|
+
retries += 1
|
|
1656
|
+
|
|
1657
|
+
# end while retries < REQUEST_MAX_RETRIES:
|
|
1658
|
+
|
|
1659
|
+
self.logger.error(
|
|
1660
|
+
"Synchronization failed for workspace -> '%s' after %d retries.",
|
|
1661
|
+
workspace_name,
|
|
1662
|
+
retries,
|
|
1663
|
+
)
|
|
1664
|
+
return None
|
|
632
1665
|
|
|
633
1666
|
# end method definition
|
|
634
1667
|
|
|
635
1668
|
def publish_project(
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
"""
|
|
643
|
-
Publish the workspace project.
|
|
1669
|
+
self,
|
|
1670
|
+
workspace_name: str,
|
|
1671
|
+
workspace_id: str,
|
|
1672
|
+
project_name: str,
|
|
1673
|
+
project_id: str,
|
|
1674
|
+
) -> bool:
|
|
1675
|
+
"""Publish the workspace project.
|
|
644
1676
|
|
|
645
1677
|
Args:
|
|
646
|
-
workspace_name (str):
|
|
647
|
-
|
|
648
|
-
workspace_id (str):
|
|
649
|
-
|
|
1678
|
+
workspace_name (str):
|
|
1679
|
+
The name of the workspace.
|
|
1680
|
+
workspace_id (str):
|
|
1681
|
+
The workspace ID.
|
|
1682
|
+
project_name (str):
|
|
1683
|
+
The name of the project.
|
|
1684
|
+
project_id (str):
|
|
1685
|
+
The project ID.
|
|
650
1686
|
|
|
651
1687
|
Returns:
|
|
652
|
-
|
|
1688
|
+
bool:
|
|
1689
|
+
True if successful, False if it fails after retries.
|
|
1690
|
+
|
|
653
1691
|
"""
|
|
654
1692
|
|
|
655
|
-
logger.info(
|
|
656
|
-
"Publish project -> '%s' in workspace -> '%s'...",
|
|
1693
|
+
self.logger.info(
|
|
1694
|
+
"Publish project -> '%s' (%s) in workspace -> '%s' (%s)...",
|
|
657
1695
|
project_name,
|
|
1696
|
+
project_id,
|
|
658
1697
|
workspace_name,
|
|
1698
|
+
workspace_id,
|
|
659
1699
|
)
|
|
660
1700
|
|
|
661
|
-
|
|
1701
|
+
# Validation of parameters:
|
|
1702
|
+
required_fields = {
|
|
1703
|
+
"workspace": workspace_name,
|
|
1704
|
+
"workspace ID": workspace_id,
|
|
1705
|
+
"project": project_name,
|
|
1706
|
+
"project ID": project_id,
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
for name, value in required_fields.items():
|
|
1710
|
+
if not value:
|
|
1711
|
+
self.logger.error(
|
|
1712
|
+
"Cannot publish project%s without a %s!",
|
|
1713
|
+
" -> '{}'".format(project_name) if project_name else "",
|
|
1714
|
+
name,
|
|
1715
|
+
)
|
|
1716
|
+
return None
|
|
1717
|
+
|
|
1718
|
+
project_publish_data = f"""
|
|
1719
|
+
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
662
1720
|
<SOAP:Body>
|
|
663
1721
|
<deployObject xmlns="http://schemas.cordys.com/cws/internal/buildhelper/BuildHelper/1.0" async="false" workspaceID="{workspace_id}" xmlns:c="http://schemas.cordys.com/cws/1.0">
|
|
664
1722
|
<object>
|
|
@@ -666,1055 +1724,1074 @@ class OTAWP:
|
|
|
666
1724
|
</object>
|
|
667
1725
|
</deployObject>
|
|
668
1726
|
</SOAP:Body>
|
|
669
|
-
</SOAP:Envelope>
|
|
1727
|
+
</SOAP:Envelope>
|
|
1728
|
+
"""
|
|
670
1729
|
|
|
671
1730
|
# Initialize retry parameters
|
|
672
|
-
max_retries = 10
|
|
673
1731
|
retries = 0
|
|
674
1732
|
success_indicator = "deployObjectResponse"
|
|
675
1733
|
|
|
676
|
-
while retries <
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
1734
|
+
while retries < REQUEST_MAX_RETRIES:
|
|
1735
|
+
try:
|
|
1736
|
+
response = requests.post(
|
|
1737
|
+
url=self.gateway_url(),
|
|
1738
|
+
data=project_publish_data,
|
|
1739
|
+
headers=REQUEST_HEADERS_XML,
|
|
1740
|
+
cookies=self.cookie(),
|
|
1741
|
+
timeout=SYNC_PUBLISH_REQUEST_TIMEOUT,
|
|
1742
|
+
)
|
|
1743
|
+
except requests.RequestException as req_exception:
|
|
1744
|
+
self.logger.warning(
|
|
1745
|
+
"Request to publish project -> '%s' (%s) failed with error -> %s. Retry in %d seconds...",
|
|
1746
|
+
project_name,
|
|
1747
|
+
project_id,
|
|
1748
|
+
str(req_exception),
|
|
1749
|
+
REQUEST_RETRY_DELAY,
|
|
1750
|
+
)
|
|
1751
|
+
retries += 1
|
|
1752
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
1753
|
+
continue
|
|
684
1754
|
|
|
685
1755
|
# Check if the response is successful
|
|
686
1756
|
if response.ok:
|
|
687
|
-
# Check if the response contains the success indicator
|
|
688
1757
|
if success_indicator in response.text:
|
|
689
|
-
logger.info(
|
|
690
|
-
"Successfully published project -> '%s' in workspace -> '%s'",
|
|
1758
|
+
self.logger.info(
|
|
1759
|
+
"Successfully published project -> '%s' (%s) in workspace -> '%s' (%s)",
|
|
691
1760
|
project_name,
|
|
1761
|
+
project_id,
|
|
692
1762
|
workspace_name,
|
|
1763
|
+
workspace_id,
|
|
693
1764
|
)
|
|
694
1765
|
return True
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
1766
|
+
else:
|
|
1767
|
+
self.logger.warning(
|
|
1768
|
+
"Expected success indicator -> '%s' but it was not found in response. Retrying in 30 seconds... (Attempt %d of %d)",
|
|
1769
|
+
success_indicator,
|
|
1770
|
+
retries + 1,
|
|
1771
|
+
REQUEST_MAX_RETRIES,
|
|
1772
|
+
)
|
|
1773
|
+
elif response.status_code == 401:
|
|
1774
|
+
# Check for session expiry and retry authentication
|
|
1775
|
+
self.logger.warning("Session has expired - re-authenticating...")
|
|
1776
|
+
self.authenticate(revalidate=True)
|
|
1777
|
+
else:
|
|
1778
|
+
self.logger.error(
|
|
1779
|
+
"Unexpected error (status code -> %d). Retrying in 30 seconds... (Attempt %d of %d)",
|
|
1780
|
+
response.status_code,
|
|
700
1781
|
retries + 1,
|
|
701
|
-
|
|
1782
|
+
REQUEST_MAX_RETRIES,
|
|
702
1783
|
)
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
self.authenticate(revalidate=True)
|
|
711
|
-
retries += 1
|
|
712
|
-
continue
|
|
1784
|
+
self.logger.debug(
|
|
1785
|
+
"SOAP message -> %s",
|
|
1786
|
+
response.text,
|
|
1787
|
+
)
|
|
1788
|
+
self.sync_workspace(workspace_name=workspace_name, workspace_id=workspace_id)
|
|
1789
|
+
retries += 1
|
|
1790
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
713
1791
|
|
|
714
|
-
|
|
715
|
-
logger.error(
|
|
716
|
-
"Error publishing project -> '%s' in workspace -> '%s'; response -> %s",
|
|
717
|
-
project_name,
|
|
718
|
-
workspace_name,
|
|
719
|
-
response.text,
|
|
720
|
-
)
|
|
721
|
-
break
|
|
1792
|
+
# end while retries < REQUEST_MAX_RETRIES:
|
|
722
1793
|
|
|
723
1794
|
# After reaching the maximum number of retries, log failure and return False
|
|
724
|
-
logger.error(
|
|
1795
|
+
self.logger.error(
|
|
725
1796
|
"Max retries reached. Failed to publish project -> '%s' in workspace -> '%s'.",
|
|
726
1797
|
project_name,
|
|
727
1798
|
workspace_name,
|
|
728
1799
|
)
|
|
1800
|
+
|
|
729
1801
|
return False
|
|
730
1802
|
|
|
731
1803
|
# end method definition
|
|
732
1804
|
|
|
733
|
-
def create_priority(
|
|
734
|
-
|
|
735
|
-
name: str,
|
|
736
|
-
description: str,
|
|
737
|
-
status: int
|
|
738
|
-
) -> dict | None:
|
|
739
|
-
""" Create Priority entity instances.
|
|
1805
|
+
def create_priority(self, name: str, description: str = "", status: int = 1) -> dict | None:
|
|
1806
|
+
"""Create Priority entity instance.
|
|
740
1807
|
|
|
741
1808
|
Args:
|
|
742
|
-
name (str):
|
|
743
|
-
|
|
744
|
-
|
|
1809
|
+
name (str):
|
|
1810
|
+
The name of the priority.
|
|
1811
|
+
description (str, optional):
|
|
1812
|
+
The description of the priority.
|
|
1813
|
+
status (int, optional):
|
|
1814
|
+
The status of the priority. Default is 1.
|
|
1815
|
+
|
|
745
1816
|
Returns:
|
|
746
|
-
dict:
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
1817
|
+
dict:
|
|
1818
|
+
Request response (dictionary) or None if the REST call fails
|
|
1819
|
+
|
|
1820
|
+
Example:
|
|
1821
|
+
{
|
|
1822
|
+
'Identity': {
|
|
1823
|
+
'Id': '327681'
|
|
1824
|
+
},
|
|
1825
|
+
'_links': {
|
|
1826
|
+
'self': {
|
|
1827
|
+
'href': '/OpentextCaseManagement/entities/Priority/items/327681'
|
|
1828
|
+
}
|
|
753
1829
|
}
|
|
754
1830
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
cookies=self.cookie(),
|
|
762
|
-
timeout=None,
|
|
763
|
-
)
|
|
764
|
-
if response.ok:
|
|
765
|
-
logger.info("Priority created successfully")
|
|
766
|
-
return self.parse_request_response(
|
|
767
|
-
response, "This can be normal during restart", False
|
|
768
|
-
)
|
|
769
|
-
if response.status_code == 401 and retries == 0:
|
|
770
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
771
|
-
self.authenticate(revalidate=True)
|
|
772
|
-
retries += 1
|
|
773
|
-
logger.error(response.text)
|
|
1831
|
+
|
|
1832
|
+
"""
|
|
1833
|
+
|
|
1834
|
+
# Sanity checks as the parameters come directly from payload:
|
|
1835
|
+
if not name:
|
|
1836
|
+
self.logger.error("Cannot create a priority without a name!")
|
|
774
1837
|
return None
|
|
775
1838
|
|
|
1839
|
+
create_priority_data = {
|
|
1840
|
+
"Properties": {"Name": name, "Description": description, "Status": status},
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
request_url = self.get_create_priority_url()
|
|
1844
|
+
|
|
1845
|
+
return self.do_request(
|
|
1846
|
+
url=request_url,
|
|
1847
|
+
method="POST",
|
|
1848
|
+
headers=REQUEST_HEADERS_JSON,
|
|
1849
|
+
cookies=self.cookie(),
|
|
1850
|
+
json_data=create_priority_data,
|
|
1851
|
+
timeout=REQUEST_TIMEOUT,
|
|
1852
|
+
failure_message="Request to create priority -> '{}' failed".format(name),
|
|
1853
|
+
)
|
|
1854
|
+
|
|
776
1855
|
# end method definition
|
|
777
1856
|
|
|
778
|
-
def
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
""" Get all priorities from entity
|
|
1857
|
+
def get_priorities(self) -> dict | None:
|
|
1858
|
+
"""Get all priorities from entity.
|
|
1859
|
+
|
|
782
1860
|
Args:
|
|
783
1861
|
None
|
|
1862
|
+
|
|
784
1863
|
Returns:
|
|
785
|
-
dict:
|
|
1864
|
+
dict:
|
|
1865
|
+
Request response (dictionary with priority values) or None if the REST call fails.
|
|
1866
|
+
|
|
1867
|
+
Example:
|
|
1868
|
+
{
|
|
1869
|
+
'page': {
|
|
1870
|
+
'skip': 0,
|
|
1871
|
+
'top': 0,
|
|
1872
|
+
'count': 4,
|
|
1873
|
+
'ftsEnabled': False
|
|
1874
|
+
},
|
|
1875
|
+
'_links': {
|
|
1876
|
+
'self': {
|
|
1877
|
+
'href': '/OpentextCaseManagement/entities/Priority/lists/PriorityList'
|
|
1878
|
+
},
|
|
1879
|
+
'first': {
|
|
1880
|
+
'href': '/OpentextCaseManagement/entities/Priority/lists/PriorityList'
|
|
1881
|
+
}
|
|
1882
|
+
},
|
|
1883
|
+
'_embedded': {
|
|
1884
|
+
'PriorityList': {
|
|
1885
|
+
'PriorityList': [
|
|
1886
|
+
{
|
|
1887
|
+
'_links': {
|
|
1888
|
+
'href': '/OpentextCaseManagement/entities/Priority/items/1'
|
|
1889
|
+
},
|
|
1890
|
+
'Properties': {
|
|
1891
|
+
'Name': 'High',
|
|
1892
|
+
'Description': 'High',
|
|
1893
|
+
'Status': 1
|
|
1894
|
+
}
|
|
1895
|
+
},
|
|
1896
|
+
{
|
|
1897
|
+
'_links': {'item': {...}},
|
|
1898
|
+
'Properties': {'Name': 'Medium', 'Description': 'Medium', 'Status': 1}
|
|
1899
|
+
},
|
|
1900
|
+
{
|
|
1901
|
+
'_links': {'item': {...}},
|
|
1902
|
+
'Properties': {'Name': 'Low', 'Description': 'Low', 'Status': 1}
|
|
1903
|
+
},
|
|
1904
|
+
{
|
|
1905
|
+
'_links': {'item': {...}},
|
|
1906
|
+
'Properties': {'Name': 'Marc Test 1', 'Description': 'Marc Test 1 Description', 'Status': 1}
|
|
1907
|
+
}
|
|
1908
|
+
]
|
|
1909
|
+
}
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
|
|
786
1913
|
"""
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
)
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
)
|
|
799
|
-
if not authenticate_dict:
|
|
800
|
-
return None
|
|
801
|
-
return authenticate_dict
|
|
802
|
-
if response.status_code == 401 and retries == 0:
|
|
803
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
804
|
-
self.authenticate(revalidate=True)
|
|
805
|
-
retries += 1
|
|
806
|
-
logger.error(response.text)
|
|
807
|
-
return None
|
|
1914
|
+
|
|
1915
|
+
request_url = self.get_priorities_list_url()
|
|
1916
|
+
|
|
1917
|
+
return self.do_request(
|
|
1918
|
+
url=request_url,
|
|
1919
|
+
method="GET",
|
|
1920
|
+
headers=REQUEST_HEADERS_JSON,
|
|
1921
|
+
cookies=self.cookie(),
|
|
1922
|
+
timeout=REQUEST_TIMEOUT,
|
|
1923
|
+
failure_message="Request to get priorities failed",
|
|
1924
|
+
)
|
|
808
1925
|
|
|
809
1926
|
# end method definition
|
|
810
1927
|
|
|
811
|
-
def
|
|
812
|
-
|
|
813
|
-
customer_name: str,
|
|
814
|
-
legal_business_name: str,
|
|
815
|
-
trading_name: str
|
|
816
|
-
) -> dict | None:
|
|
817
|
-
""" Create customer entity instance
|
|
1928
|
+
def get_priority_by_name(self, name: str) -> dict | None:
|
|
1929
|
+
"""Get priority entity instance by its name.
|
|
818
1930
|
|
|
819
1931
|
Args:
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1932
|
+
name (str):
|
|
1933
|
+
The name of the priority.
|
|
1934
|
+
|
|
823
1935
|
Returns:
|
|
824
|
-
dict
|
|
1936
|
+
dict | None:
|
|
1937
|
+
Returns the priority item or None if it does not exist.
|
|
1938
|
+
|
|
825
1939
|
"""
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
"TradingName": trading_name
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
retries = 0
|
|
834
|
-
while True:
|
|
835
|
-
response = requests.post(
|
|
836
|
-
url=self.create_customer_url(),
|
|
837
|
-
json=create_customer,
|
|
838
|
-
headers=REQUEST_HEADERS_JSON,
|
|
839
|
-
cookies=self.cookie(),
|
|
840
|
-
timeout=None,
|
|
841
|
-
)
|
|
842
|
-
if response.ok:
|
|
843
|
-
logger.info("Customer record created successfully")
|
|
844
|
-
return self.parse_request_response(response, "This can be normal during restart", False)
|
|
845
|
-
if response.status_code == 401 and retries == 0:
|
|
846
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
847
|
-
self.authenticate(revalidate=True)
|
|
848
|
-
retries += 1
|
|
849
|
-
logger.error(response.text)
|
|
850
|
-
return None
|
|
1940
|
+
|
|
1941
|
+
priorities = self.get_priorities()
|
|
1942
|
+
|
|
1943
|
+
return self.get_result_item(response=priorities, entity_type="PriorityList", key="Name", value=name)
|
|
851
1944
|
|
|
852
1945
|
# end method definition
|
|
853
1946
|
|
|
854
|
-
def
|
|
855
|
-
|
|
856
|
-
) -> dict | None:
|
|
857
|
-
"""get all customer entity imstances
|
|
1947
|
+
def get_priority_ids(self) -> list:
|
|
1948
|
+
"""Get all priority entity instances IDs.
|
|
858
1949
|
|
|
859
1950
|
Args:
|
|
860
1951
|
None
|
|
861
1952
|
Returns:
|
|
862
|
-
|
|
1953
|
+
list:
|
|
1954
|
+
A list with all priority IDs.
|
|
1955
|
+
|
|
863
1956
|
"""
|
|
864
1957
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
url=self.get_all_customeres_url(),
|
|
869
|
-
headers=REQUEST_HEADERS_JSON,
|
|
870
|
-
cookies=self.cookie(),
|
|
871
|
-
timeout=None,
|
|
872
|
-
)
|
|
873
|
-
if response.ok:
|
|
874
|
-
authenticate_dict = self.parse_request_response(
|
|
875
|
-
response, "This can be normal during restart", False
|
|
876
|
-
)
|
|
877
|
-
if not authenticate_dict:
|
|
878
|
-
return None
|
|
879
|
-
return authenticate_dict
|
|
880
|
-
if response.status_code == 401 and retries == 0:
|
|
881
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
882
|
-
self.authenticate(revalidate=True)
|
|
883
|
-
retries += 1
|
|
884
|
-
logger.error(response.text)
|
|
885
|
-
return None
|
|
1958
|
+
priorities = self.get_priorities()
|
|
1959
|
+
|
|
1960
|
+
return self.get_result_values(response=priorities, entity_type="PriorityList", key="id") or []
|
|
886
1961
|
|
|
887
1962
|
# end method definition
|
|
888
1963
|
|
|
889
|
-
def
|
|
1964
|
+
def create_customer(
|
|
890
1965
|
self,
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1966
|
+
customer_name: str,
|
|
1967
|
+
legal_business_name: str,
|
|
1968
|
+
trading_name: str,
|
|
894
1969
|
) -> dict | None:
|
|
895
|
-
"""
|
|
1970
|
+
"""Create customer entity instance.
|
|
896
1971
|
|
|
897
1972
|
Args:
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1973
|
+
customer_name (str):
|
|
1974
|
+
The name of the customer.
|
|
1975
|
+
legal_business_name (str):
|
|
1976
|
+
The legal business name.
|
|
1977
|
+
trading_name (str):
|
|
1978
|
+
The trading name.
|
|
1979
|
+
|
|
901
1980
|
Returns:
|
|
902
|
-
dict:
|
|
1981
|
+
dict:
|
|
1982
|
+
Request response (dictionary) or None if the REST call fails.
|
|
1983
|
+
|
|
903
1984
|
"""
|
|
904
|
-
|
|
1985
|
+
|
|
1986
|
+
# Sanity checks as the parameters come directly from payload:
|
|
1987
|
+
if not customer_name:
|
|
1988
|
+
self.logger.error("Cannot create a customer without a name!")
|
|
1989
|
+
return None
|
|
1990
|
+
|
|
1991
|
+
create_customer_data = {
|
|
905
1992
|
"Properties": {
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
}
|
|
1993
|
+
"CustomerName": customer_name,
|
|
1994
|
+
"LegalBusinessName": legal_business_name,
|
|
1995
|
+
"TradingName": trading_name,
|
|
1996
|
+
},
|
|
910
1997
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
response, "This can be normal during restart", False
|
|
924
|
-
)
|
|
925
|
-
if response.status_code == 401 and retries == 0:
|
|
926
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
927
|
-
self.authenticate(revalidate=True)
|
|
928
|
-
retries += 1
|
|
929
|
-
logger.error(response.text)
|
|
930
|
-
return None
|
|
1998
|
+
|
|
1999
|
+
request_url = self.get_create_customer_url()
|
|
2000
|
+
|
|
2001
|
+
return self.do_request(
|
|
2002
|
+
url=request_url,
|
|
2003
|
+
method="POST",
|
|
2004
|
+
headers=REQUEST_HEADERS_JSON,
|
|
2005
|
+
cookies=self.cookie(),
|
|
2006
|
+
json_data=create_customer_data,
|
|
2007
|
+
timeout=REQUEST_TIMEOUT,
|
|
2008
|
+
failure_message="Request to create customer -> '{}' failed".format(customer_name),
|
|
2009
|
+
)
|
|
931
2010
|
|
|
932
2011
|
# end method definition
|
|
933
2012
|
|
|
934
|
-
def
|
|
935
|
-
|
|
936
|
-
) -> dict | None:
|
|
937
|
-
"""get all case type entty instances
|
|
2013
|
+
def get_customers(self) -> dict | None:
|
|
2014
|
+
"""Get all customer entity instances.
|
|
938
2015
|
|
|
939
2016
|
Args:
|
|
940
2017
|
None
|
|
2018
|
+
|
|
941
2019
|
Returns:
|
|
942
|
-
dict
|
|
2020
|
+
dict | None:
|
|
2021
|
+
Request response (dictionary) or None if the REST call fails.
|
|
2022
|
+
|
|
2023
|
+
Example:
|
|
2024
|
+
{
|
|
2025
|
+
'page': {
|
|
2026
|
+
'skip': 0,
|
|
2027
|
+
'top': 0,
|
|
2028
|
+
'count': 4,
|
|
2029
|
+
'ftsEnabled': False
|
|
2030
|
+
},
|
|
2031
|
+
'_links': {
|
|
2032
|
+
'self': {
|
|
2033
|
+
'href': '/OpentextCaseManagement/entities/Customer/lists/CustomerList'
|
|
2034
|
+
},
|
|
2035
|
+
'first': {...}
|
|
2036
|
+
},
|
|
2037
|
+
'_embedded': {
|
|
2038
|
+
'CustomerList': [
|
|
2039
|
+
{
|
|
2040
|
+
'_links': {
|
|
2041
|
+
'item': {
|
|
2042
|
+
'href': '/OpentextCaseManagement/entities/Customer/items/1'
|
|
2043
|
+
}
|
|
2044
|
+
},
|
|
2045
|
+
'Properties': {
|
|
2046
|
+
'CustomerName': 'InaPlex Limited',
|
|
2047
|
+
'LegalBusinessName': 'InaPlex Limited',
|
|
2048
|
+
'TradingName': 'InaPlex Limited'
|
|
2049
|
+
}
|
|
2050
|
+
},
|
|
2051
|
+
{
|
|
2052
|
+
'_links': {...},
|
|
2053
|
+
'Properties': {...}
|
|
2054
|
+
},
|
|
2055
|
+
...
|
|
2056
|
+
]
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
|
|
943
2060
|
"""
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
)
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
)
|
|
956
|
-
if not authenticate_dict:
|
|
957
|
-
return None
|
|
958
|
-
return authenticate_dict
|
|
959
|
-
if response.status_code == 401 and retries == 0:
|
|
960
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
961
|
-
self.authenticate(revalidate=True)
|
|
962
|
-
retries += 1
|
|
963
|
-
logger.error(response.text)
|
|
964
|
-
return None
|
|
2061
|
+
|
|
2062
|
+
request_url = self.get_customers_list_url()
|
|
2063
|
+
|
|
2064
|
+
return self.do_request(
|
|
2065
|
+
url=request_url,
|
|
2066
|
+
method="GET",
|
|
2067
|
+
headers=REQUEST_HEADERS_JSON,
|
|
2068
|
+
cookies=self.cookie(),
|
|
2069
|
+
timeout=REQUEST_TIMEOUT,
|
|
2070
|
+
failure_message="Request to get customers failed",
|
|
2071
|
+
)
|
|
965
2072
|
|
|
966
2073
|
# end method definition
|
|
967
2074
|
|
|
968
|
-
def
|
|
969
|
-
|
|
970
|
-
case_prefix: str,
|
|
971
|
-
description: str,
|
|
972
|
-
name: str,
|
|
973
|
-
status: int
|
|
974
|
-
) -> dict | None:
|
|
975
|
-
"""create category entity instance
|
|
2075
|
+
def get_customer_by_name(self, name: str) -> dict | None:
|
|
2076
|
+
"""Get customer entity instance by its name.
|
|
976
2077
|
|
|
977
2078
|
Args:
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
status (str): status
|
|
2079
|
+
name (str):
|
|
2080
|
+
The name of the customer.
|
|
2081
|
+
|
|
982
2082
|
Returns:
|
|
983
|
-
dict
|
|
2083
|
+
dict | None:
|
|
2084
|
+
Returns the customer data or None if no customer with the given name exists.
|
|
2085
|
+
|
|
984
2086
|
"""
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
"Name": name,
|
|
990
|
-
"Status": status
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
retries = 0
|
|
994
|
-
while True:
|
|
995
|
-
response = requests.post(
|
|
996
|
-
url=self.create_category_url(),
|
|
997
|
-
json=create_categoty,
|
|
998
|
-
headers=REQUEST_HEADERS_JSON,
|
|
999
|
-
cookies=self.cookie(),
|
|
1000
|
-
timeout=None,
|
|
1001
|
-
)
|
|
1002
|
-
if response.ok:
|
|
1003
|
-
logger.info("Category created successfully")
|
|
1004
|
-
return self.parse_request_response(response, "This can be normal during restart", False)
|
|
1005
|
-
if response.status_code == 401 and retries == 0:
|
|
1006
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1007
|
-
self.authenticate(revalidate=True)
|
|
1008
|
-
retries += 1
|
|
1009
|
-
logger.error(response.text)
|
|
1010
|
-
return None
|
|
2087
|
+
|
|
2088
|
+
customers = self.get_customers()
|
|
2089
|
+
|
|
2090
|
+
return self.get_result_item(response=customers, entity_type="CustomerList", key="CustomerName", value=name)
|
|
1011
2091
|
|
|
1012
2092
|
# end method definition
|
|
1013
2093
|
|
|
1014
|
-
def
|
|
1015
|
-
|
|
1016
|
-
) -> dict | None:
|
|
1017
|
-
"""Get all categories entity intances
|
|
2094
|
+
def get_customer_ids(self) -> list:
|
|
2095
|
+
"""Get all customer entity instances IDs.
|
|
1018
2096
|
|
|
1019
2097
|
Args:
|
|
1020
2098
|
None
|
|
1021
2099
|
Returns:
|
|
1022
|
-
|
|
2100
|
+
list:
|
|
2101
|
+
A list of all customer IDs.
|
|
2102
|
+
|
|
1023
2103
|
"""
|
|
1024
2104
|
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
url=self.get_all_categories_url(),
|
|
1029
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1030
|
-
cookies=self.cookie(),
|
|
1031
|
-
timeout=None,
|
|
1032
|
-
)
|
|
1033
|
-
if response.ok:
|
|
1034
|
-
authenticate_dict = self.parse_request_response(
|
|
1035
|
-
response, "This can be normal during restart", False
|
|
1036
|
-
)
|
|
1037
|
-
if not authenticate_dict:
|
|
1038
|
-
return None
|
|
1039
|
-
return authenticate_dict
|
|
1040
|
-
if response.status_code == 401 and retries == 0:
|
|
1041
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1042
|
-
self.authenticate(revalidate=True)
|
|
1043
|
-
retries += 1
|
|
1044
|
-
logger.error(response.text)
|
|
1045
|
-
return None
|
|
2105
|
+
customers = self.get_customers()
|
|
2106
|
+
|
|
2107
|
+
return self.get_result_values(response=customers, entity_type="CustomerList", key="id") or []
|
|
1046
2108
|
|
|
1047
2109
|
# end method definition
|
|
1048
2110
|
|
|
1049
|
-
def
|
|
1050
|
-
|
|
1051
|
-
name: str,
|
|
1052
|
-
description: str,
|
|
1053
|
-
status: int,
|
|
1054
|
-
parentid: int
|
|
1055
|
-
) -> dict | None:
|
|
1056
|
-
""" create sub_categoy entity istances
|
|
2111
|
+
def create_case_type(self, name: str, description: str = "", status: int = 1) -> dict | None:
|
|
2112
|
+
"""Create case type entity instances.
|
|
1057
2113
|
|
|
1058
2114
|
Args:
|
|
1059
|
-
name (str):
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
2115
|
+
name (str):
|
|
2116
|
+
The name of the case type.
|
|
2117
|
+
description (str, optional):
|
|
2118
|
+
The description of the case type.
|
|
2119
|
+
status (int, optional): status
|
|
2120
|
+
|
|
1063
2121
|
Returns:
|
|
1064
|
-
dict:
|
|
2122
|
+
dict:
|
|
2123
|
+
Request response (dictionary) or None if the REST call fails.
|
|
2124
|
+
|
|
1065
2125
|
"""
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
"
|
|
1070
|
-
"Status": status
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
retries = 0
|
|
1074
|
-
while True:
|
|
1075
|
-
base_url = self.baseurl()
|
|
1076
|
-
endpoint = "/app/entityRestService/api/OpentextCaseManagement/entities/Category/items/"
|
|
1077
|
-
child_path = "/childEntities/SubCategory?defaultinst_ct=abcd"
|
|
1078
|
-
response = requests.post(
|
|
1079
|
-
url=base_url + endpoint + str(parentid) + child_path,
|
|
1080
|
-
json=create_sub_categoty,
|
|
1081
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1082
|
-
cookies=self.cookie(),
|
|
1083
|
-
timeout=None,
|
|
1084
|
-
)
|
|
1085
|
-
if response.ok:
|
|
1086
|
-
logger.info("Sub category created successfully")
|
|
1087
|
-
return self.parse_request_response(
|
|
1088
|
-
response, "This can be normal during restart", False
|
|
1089
|
-
)
|
|
1090
|
-
if response.status_code == 401 and retries == 0:
|
|
1091
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1092
|
-
self.authenticate(revalidate=True)
|
|
1093
|
-
retries += 1
|
|
1094
|
-
logger.error(response.text)
|
|
2126
|
+
|
|
2127
|
+
# Sanity checks as the parameters come directly from payload:
|
|
2128
|
+
if not name:
|
|
2129
|
+
self.logger.error("Cannot create a case type without a name!")
|
|
1095
2130
|
return None
|
|
1096
2131
|
|
|
1097
|
-
|
|
2132
|
+
create_case_type_data = {
|
|
2133
|
+
"Properties": {"Name": name, "Description": description, "Status": status},
|
|
2134
|
+
}
|
|
1098
2135
|
|
|
1099
|
-
|
|
1100
|
-
self,
|
|
1101
|
-
parentid: int
|
|
1102
|
-
) -> dict | None:
|
|
1103
|
-
"""Get all sub categeries entity instances
|
|
2136
|
+
request_url = self.get_create_casetype_url()
|
|
1104
2137
|
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
child_path = "/childEntities/SubCategory"
|
|
1115
|
-
response = requests.get(
|
|
1116
|
-
url=base_url + endpoint + str(parentid) + child_path,
|
|
1117
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1118
|
-
cookies=self.cookie(),
|
|
1119
|
-
timeout=None,
|
|
1120
|
-
)
|
|
1121
|
-
if response.ok:
|
|
1122
|
-
authenticate_dict = self.parse_request_response(
|
|
1123
|
-
response, "This can be normal during restart", False
|
|
1124
|
-
)
|
|
1125
|
-
if not authenticate_dict:
|
|
1126
|
-
return None
|
|
1127
|
-
return authenticate_dict
|
|
1128
|
-
if response.status_code == 401 and retries == 0:
|
|
1129
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1130
|
-
self.authenticate(revalidate=True)
|
|
1131
|
-
retries += 1
|
|
1132
|
-
logger.error(response.text)
|
|
1133
|
-
return None
|
|
2138
|
+
return self.do_request(
|
|
2139
|
+
url=request_url,
|
|
2140
|
+
method="POST",
|
|
2141
|
+
headers=REQUEST_HEADERS_JSON,
|
|
2142
|
+
cookies=self.cookie(),
|
|
2143
|
+
json_data=create_case_type_data,
|
|
2144
|
+
timeout=REQUEST_TIMEOUT,
|
|
2145
|
+
failure_message="Request to create case type -> '{}' failed".format(name),
|
|
2146
|
+
)
|
|
1134
2147
|
|
|
1135
2148
|
# end method definition
|
|
1136
2149
|
|
|
1137
|
-
def
|
|
1138
|
-
|
|
1139
|
-
subject: str,
|
|
1140
|
-
description: str,
|
|
1141
|
-
loan_amount: str,
|
|
1142
|
-
loan_duration_in_months: str,
|
|
1143
|
-
category: str,
|
|
1144
|
-
subcategory: str,
|
|
1145
|
-
piority: str,
|
|
1146
|
-
service: str,
|
|
1147
|
-
customer: str
|
|
1148
|
-
|
|
1149
|
-
) -> dict | None:
|
|
1150
|
-
"""create loan entity instance
|
|
2150
|
+
def get_case_types(self) -> dict | None:
|
|
2151
|
+
"""Get all case type entity instances.
|
|
1151
2152
|
|
|
1152
2153
|
Args:
|
|
1153
|
-
|
|
1154
|
-
description (str): description
|
|
1155
|
-
loan_amount (str): loan_amount
|
|
1156
|
-
loan_duration_in_months (str): loan_duration_in_months
|
|
1157
|
-
category (str): category
|
|
1158
|
-
subcategory (str): subcategory
|
|
1159
|
-
piority (str): piority
|
|
1160
|
-
service (str): service
|
|
1161
|
-
customer (str): customer
|
|
1162
|
-
Returns:
|
|
1163
|
-
dict: Request response (dictionary) or None if the REST call fails
|
|
1164
|
-
"""
|
|
1165
|
-
create_loan = f"""<SOAP:Envelope xmlns:SOAP=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n
|
|
1166
|
-
<SOAP:Body>\r\n
|
|
1167
|
-
<CreateCase xmlns=\"http://schemas/OpentextCaseManagement/Case/operations\">\r\n
|
|
1168
|
-
<ns0:Case-create xmlns:ns0=\"http://schemas/OpentextCaseManagement/Case\">\r\n
|
|
1169
|
-
<ns0:Subject>{subject}</ns0:Subject>\r\n
|
|
1170
|
-
<ns0:Description>{description}</ns0:Description>\r\n
|
|
1171
|
-
<ns0:LoanAmount>{loan_amount}</ns0:LoanAmount>\r\n
|
|
1172
|
-
<ns0:LoanDurationInMonths>{loan_duration_in_months}</ns0:LoanDurationInMonths>\r\n
|
|
1173
|
-
\r\n
|
|
1174
|
-
<ns0:CaseType>\r\n
|
|
1175
|
-
<ns1:CaseType-id xmlns:ns1=\"http://schemas/OpentextCaseManagement/CaseType\">\r\n
|
|
1176
|
-
<ns1:Id>{service}</ns1:Id>\r\n
|
|
1177
|
-
</ns1:CaseType-id>\r\n
|
|
1178
|
-
</ns0:CaseType>\r\n
|
|
1179
|
-
\r\n
|
|
1180
|
-
<ns0:Category>\r\n
|
|
1181
|
-
<ns2:Category-id xmlns:ns2=\"http://schemas/OpentextCaseManagement/Category\">\r\n
|
|
1182
|
-
<ns2:Id>{category}</ns2:Id>\r\n
|
|
1183
|
-
</ns2:Category-id>\r\n
|
|
1184
|
-
</ns0:Category>\r\n
|
|
1185
|
-
\r\n
|
|
1186
|
-
<ns0:SubCategory>\r\n
|
|
1187
|
-
<ns5:SubCategory-id xmlns:ns5=\"http://schemas/OpentextCaseManagement/Category.SubCategory\">\r\n
|
|
1188
|
-
<ns5:Id>{category}</ns5:Id>\r\n
|
|
1189
|
-
<ns5:Id1>{subcategory}</ns5:Id1>\r\n
|
|
1190
|
-
</ns5:SubCategory-id>\r\n
|
|
1191
|
-
</ns0:SubCategory>\r\n
|
|
1192
|
-
\r\n
|
|
1193
|
-
<ns0:Priority>\r\n
|
|
1194
|
-
<ns3:Priority-id xmlns:ns3=\"http://schemas/OpentextCaseManagement/Priority\">\r\n
|
|
1195
|
-
<ns3:Id>{piority}</ns3:Id>\r\n
|
|
1196
|
-
</ns3:Priority-id>\r\n
|
|
1197
|
-
</ns0:Priority>\r\n
|
|
1198
|
-
\r\n
|
|
1199
|
-
<ns0:ToCustomer>\r\n
|
|
1200
|
-
<ns9:Customer-id xmlns:ns9=\"http://schemas/OpentextCaseManagement/Customer\">\r\n
|
|
1201
|
-
<ns9:Id>{customer}</ns9:Id>\r\n
|
|
1202
|
-
</ns9:Customer-id>\r\n
|
|
1203
|
-
</ns0:ToCustomer>\r\n
|
|
1204
|
-
\r\n
|
|
1205
|
-
</ns0:Case-create>\r\n
|
|
1206
|
-
</CreateCase>\r\n
|
|
1207
|
-
</SOAP:Body>\r\n
|
|
1208
|
-
</SOAP:Envelope>"""
|
|
2154
|
+
None
|
|
1209
2155
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
2156
|
+
Returns:
|
|
2157
|
+
dict:
|
|
2158
|
+
Request response (dictionary) or None if the REST call fails.
|
|
2159
|
+
|
|
2160
|
+
Example:
|
|
2161
|
+
{
|
|
2162
|
+
'page': {
|
|
2163
|
+
'skip': 0,
|
|
2164
|
+
'top': 0,
|
|
2165
|
+
'count': 5,
|
|
2166
|
+
'ftsEnabled': False
|
|
2167
|
+
},
|
|
2168
|
+
'_links': {
|
|
2169
|
+
'self': {
|
|
2170
|
+
'href': '/OpentextCaseManagement/entities/CaseType/lists/AllCaseTypes'
|
|
2171
|
+
},
|
|
2172
|
+
'first': {...}
|
|
2173
|
+
},
|
|
2174
|
+
'_embedded': {
|
|
2175
|
+
'AllCaseTypes': [
|
|
2176
|
+
{
|
|
2177
|
+
'_links': {
|
|
2178
|
+
'item': {
|
|
2179
|
+
'href': '/OpentextCaseManagement/entities/CaseType/items/1'
|
|
2180
|
+
}
|
|
2181
|
+
},
|
|
2182
|
+
'Properties': {
|
|
2183
|
+
'Name': 'Query',
|
|
2184
|
+
'Description': 'Query',
|
|
2185
|
+
'Status': 1
|
|
2186
|
+
}
|
|
2187
|
+
},
|
|
2188
|
+
{
|
|
2189
|
+
'_links': {...},
|
|
2190
|
+
'Properties': {...}
|
|
2191
|
+
},
|
|
2192
|
+
...
|
|
2193
|
+
]
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
|
|
2197
|
+
"""
|
|
2198
|
+
|
|
2199
|
+
request_url = self.get_casetypes_list_url()
|
|
2200
|
+
|
|
2201
|
+
return self.do_request(
|
|
2202
|
+
url=request_url,
|
|
2203
|
+
method="GET",
|
|
2204
|
+
headers=REQUEST_HEADERS_JSON,
|
|
2205
|
+
cookies=self.cookie(),
|
|
2206
|
+
timeout=REQUEST_TIMEOUT,
|
|
2207
|
+
failure_message="Request to get case types failed",
|
|
2208
|
+
)
|
|
1228
2209
|
|
|
1229
2210
|
# end method definition
|
|
1230
2211
|
|
|
1231
|
-
def
|
|
1232
|
-
|
|
1233
|
-
) -> dict | None:
|
|
1234
|
-
"""get all loan entity instances
|
|
2212
|
+
def get_case_type_by_name(self, name: str) -> dict | None:
|
|
2213
|
+
"""Get case type entity instance by its name.
|
|
1235
2214
|
|
|
1236
2215
|
Args:
|
|
1237
|
-
|
|
2216
|
+
name (str):
|
|
2217
|
+
The name of the case type.
|
|
2218
|
+
|
|
1238
2219
|
Returns:
|
|
1239
|
-
dict
|
|
2220
|
+
dict | None:
|
|
2221
|
+
Returns the case type data or None if no case type with the given name exists.
|
|
2222
|
+
|
|
1240
2223
|
"""
|
|
1241
2224
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
url=self.get_all_loans_url(),
|
|
1246
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1247
|
-
cookies=self.cookie(),
|
|
1248
|
-
timeout=None,
|
|
1249
|
-
)
|
|
1250
|
-
if response.ok:
|
|
1251
|
-
authenticate_dict = self.parse_request_response(
|
|
1252
|
-
response, "This can be normal during restart", False
|
|
1253
|
-
)
|
|
1254
|
-
if not authenticate_dict:
|
|
1255
|
-
return None
|
|
1256
|
-
return authenticate_dict
|
|
1257
|
-
if response.status_code == 401 and retries == 0:
|
|
1258
|
-
logger.warning("Session has expired - try to re-authenticate...")
|
|
1259
|
-
self.authenticate(revalidate=True)
|
|
1260
|
-
retries += 1
|
|
1261
|
-
else:
|
|
1262
|
-
logger.error(response.text)
|
|
1263
|
-
return None
|
|
2225
|
+
case_types = self.get_case_types()
|
|
2226
|
+
|
|
2227
|
+
return self.get_result_item(response=case_types, entity_type="AllCaseTypes", key="Name", value=name)
|
|
1264
2228
|
|
|
1265
2229
|
# end method definition
|
|
1266
2230
|
|
|
1267
|
-
def
|
|
1268
|
-
|
|
1269
|
-
response: str,
|
|
1270
|
-
workspace_name: str
|
|
1271
|
-
) -> bool:
|
|
1272
|
-
"""
|
|
1273
|
-
Verify if the workspace exists or was created successfully.
|
|
2231
|
+
def get_case_type_ids(self) -> list:
|
|
2232
|
+
"""Get All CaseType entity instances IDs.
|
|
1274
2233
|
|
|
1275
2234
|
Args:
|
|
1276
|
-
|
|
1277
|
-
workspace_name (str): The name of the workspace.
|
|
2235
|
+
None
|
|
1278
2236
|
|
|
1279
2237
|
Returns:
|
|
1280
|
-
|
|
2238
|
+
list:
|
|
2239
|
+
List of all case type IDs.
|
|
2240
|
+
|
|
1281
2241
|
"""
|
|
1282
2242
|
|
|
1283
|
-
|
|
1284
|
-
logger.info(
|
|
1285
|
-
"The workspace already exists or was created with the name -> '%s'",
|
|
1286
|
-
workspace_name,
|
|
1287
|
-
)
|
|
1288
|
-
return True
|
|
2243
|
+
case_types = self.get_case_types()
|
|
1289
2244
|
|
|
1290
|
-
|
|
1291
|
-
"The workspace -> '%s' does not exist or was not created. Please verify configurtion!",
|
|
1292
|
-
workspace_name,
|
|
1293
|
-
)
|
|
1294
|
-
return False
|
|
2245
|
+
return self.get_result_values(response=case_types, entity_type="AllCaseTypes", key="id") or []
|
|
1295
2246
|
|
|
1296
2247
|
# end method definition
|
|
1297
2248
|
|
|
1298
|
-
def
|
|
2249
|
+
def create_category(
|
|
1299
2250
|
self,
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
Returns:
|
|
1307
|
-
bool: return true if workspace exist else return false
|
|
1308
|
-
"""
|
|
2251
|
+
case_prefix: str,
|
|
2252
|
+
name: str,
|
|
2253
|
+
description: str,
|
|
2254
|
+
status: int = 1,
|
|
2255
|
+
) -> dict | None:
|
|
2256
|
+
"""Create category entity instance.
|
|
1309
2257
|
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
)
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
2258
|
+
Args:
|
|
2259
|
+
case_prefix (str):
|
|
2260
|
+
The prefix for the case.
|
|
2261
|
+
description (str):
|
|
2262
|
+
The description for the category.
|
|
2263
|
+
name (str):
|
|
2264
|
+
The name of the category.
|
|
2265
|
+
status (int):
|
|
2266
|
+
The status code.
|
|
1319
2267
|
|
|
1320
|
-
|
|
2268
|
+
Returns:
|
|
2269
|
+
dict:
|
|
2270
|
+
Request response (dictionary) or None if the REST call fails.
|
|
2271
|
+
|
|
2272
|
+
Example:
|
|
2273
|
+
{
|
|
2274
|
+
'Identity': {
|
|
2275
|
+
'Id': '327681'
|
|
2276
|
+
},
|
|
2277
|
+
'_links': {
|
|
2278
|
+
'self': {
|
|
2279
|
+
href': '/OpentextCaseManagement/entities/Category/items/327681'
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
1321
2283
|
|
|
1322
|
-
def create_workspace_with_retry(self, workspace_name: str, workspace_gui_id: str) -> dict | None:
|
|
1323
|
-
"""
|
|
1324
|
-
Calls create_workspace and retries if the response contains specific error messages.
|
|
1325
|
-
Retries until the response does not contain the errors or a max retry limit is reached.
|
|
1326
2284
|
"""
|
|
1327
2285
|
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
"Service Group Lookup failure"
|
|
1333
|
-
]
|
|
2286
|
+
# Sanity checks as the parameters come directly from payload:
|
|
2287
|
+
if not name:
|
|
2288
|
+
self.logger.error("Cannot create a category without a name!")
|
|
2289
|
+
return None
|
|
1334
2290
|
|
|
1335
|
-
|
|
1336
|
-
|
|
2291
|
+
create_category_data = {
|
|
2292
|
+
"Properties": {
|
|
2293
|
+
"CasePrefix": case_prefix,
|
|
2294
|
+
"Description": description,
|
|
2295
|
+
"Name": name,
|
|
2296
|
+
"Status": status,
|
|
2297
|
+
},
|
|
2298
|
+
}
|
|
1337
2299
|
|
|
1338
|
-
|
|
1339
|
-
if any(error_message in response for error_message in error_messages):
|
|
1340
|
-
logger.info("Workspace service error, waiting 60 seconds to retry... (Retry %d of %d)", retries + 1, max_retries)
|
|
1341
|
-
time.sleep(60)
|
|
1342
|
-
retries += 1
|
|
1343
|
-
else:
|
|
1344
|
-
logger.info("Collaborative Workspace Service Container is ready")
|
|
1345
|
-
return response
|
|
2300
|
+
request_url = self.get_create_category_url()
|
|
1346
2301
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
"
|
|
1350
|
-
|
|
2302
|
+
return self.do_request(
|
|
2303
|
+
url=request_url,
|
|
2304
|
+
method="POST",
|
|
2305
|
+
headers=REQUEST_HEADERS_JSON,
|
|
2306
|
+
cookies=self.cookie(),
|
|
2307
|
+
json_data=create_category_data,
|
|
2308
|
+
timeout=REQUEST_TIMEOUT,
|
|
2309
|
+
failure_message="Request to create category -> '{}' failed".format(name),
|
|
1351
2310
|
)
|
|
1352
|
-
return response
|
|
1353
2311
|
|
|
1354
2312
|
# end method definition
|
|
1355
2313
|
|
|
1356
|
-
def
|
|
1357
|
-
"""
|
|
2314
|
+
def get_categories(self) -> dict | None:
|
|
2315
|
+
"""Get all categories entity instances.
|
|
2316
|
+
|
|
1358
2317
|
Args:
|
|
1359
2318
|
None
|
|
1360
2319
|
Returns:
|
|
1361
|
-
None
|
|
2320
|
+
dict | None:
|
|
2321
|
+
Request response (dictionary) or None if the REST call fails.
|
|
2322
|
+
|
|
2323
|
+
Example:
|
|
2324
|
+
{
|
|
2325
|
+
'page': {
|
|
2326
|
+
'skip': 0,
|
|
2327
|
+
'top': 0,
|
|
2328
|
+
'count': 3,
|
|
2329
|
+
'ftsEnabled': False
|
|
2330
|
+
},
|
|
2331
|
+
'_links': {
|
|
2332
|
+
'self': {
|
|
2333
|
+
'href': '/OpentextCaseManagement/entities/Category/lists/CategoryList'
|
|
2334
|
+
},
|
|
2335
|
+
'first': {...}
|
|
2336
|
+
},
|
|
2337
|
+
'_embedded': {
|
|
2338
|
+
'CategoryList': [
|
|
2339
|
+
{
|
|
2340
|
+
'_links': {
|
|
2341
|
+
'item': {
|
|
2342
|
+
'href': '/OpentextCaseManagement/entities/Category/items/1'
|
|
2343
|
+
}
|
|
2344
|
+
},
|
|
2345
|
+
'Properties': {
|
|
2346
|
+
'Name': 'Short Term Loan',
|
|
2347
|
+
'Description': 'Short Term Loan',
|
|
2348
|
+
'CasePrefix': 'LOAN',
|
|
2349
|
+
'Status': 1
|
|
2350
|
+
}
|
|
2351
|
+
},
|
|
2352
|
+
{
|
|
2353
|
+
'_links': {...},
|
|
2354
|
+
'Properties': {...}
|
|
2355
|
+
},
|
|
2356
|
+
{
|
|
2357
|
+
'_links': {...},
|
|
2358
|
+
'Properties': {...}
|
|
2359
|
+
}
|
|
2360
|
+
]
|
|
2361
|
+
}
|
|
2362
|
+
}
|
|
2363
|
+
|
|
1362
2364
|
"""
|
|
1363
2365
|
|
|
1364
|
-
|
|
1365
|
-
category_resp_dict = []
|
|
1366
|
-
if not self.verify_category_exists("Short Term Loan"):
|
|
1367
|
-
self.create_category("LOAN","Short Term Loan","Short Term Loan",1)
|
|
1368
|
-
if not self.verify_category_exists("Long Term Loan"):
|
|
1369
|
-
self.create_category("LOAN","Long Term Loan","Long Term Loan",1)
|
|
1370
|
-
if not self.verify_category_exists("Flexi Loan"):
|
|
1371
|
-
self.create_category("LOAN","Flexi Loan","Flexi Loan",1)
|
|
1372
|
-
category_resp_dict = self.get_category_lists()
|
|
1373
|
-
logger.debug(" RUNTIME -->> Category instance creation ended")
|
|
1374
|
-
|
|
1375
|
-
############################# Sub category
|
|
1376
|
-
logger.debug(" RUNTIME -->> Sub Category instance creation started ........")
|
|
1377
|
-
stl = 0
|
|
1378
|
-
ltl = 0
|
|
1379
|
-
fl = 0
|
|
1380
|
-
if not self.verify_sub_category_exists("Business",0,category_resp_dict):
|
|
1381
|
-
response_dict = self.create_sub_categoy("Business","Business",1,category_resp_dict[0])
|
|
1382
|
-
stl = response_dict["Identity"]["Id"]
|
|
1383
|
-
logger.info("Sub category id stl: %s ", stl)
|
|
1384
|
-
else:
|
|
1385
|
-
stl = self.return_sub_category_exists_id("Business",0,category_resp_dict)
|
|
1386
|
-
logger.info("Sub category id stl -> %s ", stl)
|
|
2366
|
+
request_url = self.get_categories_list_url()
|
|
1387
2367
|
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
response_dict= self.create_sub_categoy("Business","Business",1,category_resp_dict[2])
|
|
1397
|
-
fl = response_dict["Identity"]["Id"]
|
|
1398
|
-
logger.info("Sub category id fl -> %s ", fl)
|
|
1399
|
-
else:
|
|
1400
|
-
fl = self.return_sub_category_exists_id("Business",2,category_resp_dict)
|
|
1401
|
-
logger.info("Sub category id fl -> %s ", fl)
|
|
1402
|
-
logger.debug(" RUNTIME -->> Sub Category instance creation ended")
|
|
1403
|
-
|
|
1404
|
-
############################# Case Types
|
|
1405
|
-
logger.debug(" RUNTIME -->> Case Types instance creation started ........")
|
|
1406
|
-
case_type_list = []
|
|
1407
|
-
|
|
1408
|
-
if not self.vverify_case_type_exists("Query"):
|
|
1409
|
-
self.create_case_type("Query","Query",1)
|
|
1410
|
-
if not self.vverify_case_type_exists("Help"):
|
|
1411
|
-
self.create_case_type("Help","Help",1)
|
|
1412
|
-
if not self.vverify_case_type_exists("Update Contact Details"):
|
|
1413
|
-
self.create_case_type("Update Contact Details","Update Contact Details",1)
|
|
1414
|
-
if not self.vverify_case_type_exists("New Loan Request"):
|
|
1415
|
-
self.create_case_type("New Loan Request","New Loan Request",1)
|
|
1416
|
-
if not self.vverify_case_type_exists("Loan Closure"):
|
|
1417
|
-
self.create_case_type("Loan Closure","Loan Closure",1)
|
|
1418
|
-
case_type_list = self.get_case_type_lists()
|
|
1419
|
-
logger.debug(" RUNTIME -->> Case Types instance creation ended")
|
|
1420
|
-
|
|
1421
|
-
############################# CUSTMOR
|
|
1422
|
-
logger.debug(" RUNTIME -->> Customer instance creation stated ........")
|
|
1423
|
-
customer_list = []
|
|
1424
|
-
if not self.verify_customer_exists("InaPlex Limited"):
|
|
1425
|
-
self.create_customer("InaPlex Limited","InaPlex Limited","InaPlex Limited")
|
|
1426
|
-
|
|
1427
|
-
if not self.verify_customer_exists("Interwoven, Inc"):
|
|
1428
|
-
self.create_customer("Interwoven, Inc","Interwoven, Inc","Interwoven, Inc")
|
|
1429
|
-
|
|
1430
|
-
if not self.verify_customer_exists("Jones Lang LaSalle"):
|
|
1431
|
-
self.create_customer("Jones Lang LaSalle","Jones Lang LaSalle","Jones Lang LaSalle")
|
|
1432
|
-
|
|
1433
|
-
if not self.verify_customer_exists("Key Point Consulting"):
|
|
1434
|
-
self.create_customer("Key Point Consulting","Key Point Consulting","Key Point Consulting")
|
|
1435
|
-
|
|
1436
|
-
customer_list = self.get_customer_lists()
|
|
1437
|
-
logger.debug(" RUNTIME -->> Customer instance creation ended")
|
|
1438
|
-
|
|
1439
|
-
######################################## PRIORITY
|
|
1440
|
-
logger.debug(" RUNTIME -->> priority instance creation started ........")
|
|
1441
|
-
prioity_list = []
|
|
1442
|
-
if not self.verify_priority_exists("High"):
|
|
1443
|
-
self.create_priority("High","High",1)
|
|
1444
|
-
if not self.verify_priority_exists("Medium"):
|
|
1445
|
-
self.create_priority("Medium","Medium",1)
|
|
1446
|
-
if not self.verify_priority_exists("Low"):
|
|
1447
|
-
self.create_priority("Low","Low",1)
|
|
1448
|
-
prioity_list = self.get_priority_lists()
|
|
1449
|
-
logger.debug(" RUNTIME -->> priority instance creation ended")
|
|
1450
|
-
|
|
1451
|
-
############################# LOAN
|
|
1452
|
-
loan_for_business = "Loan for Business1"
|
|
1453
|
-
loan_for_corporate_business = "Loan for Corporate Business1"
|
|
1454
|
-
loan_for_business_loan_request = "Loan for Business Loan Request1"
|
|
1455
|
-
|
|
1456
|
-
logger.debug(" RUNTIME -->> loan instance creation started ........")
|
|
1457
|
-
loan_resp_dict = self.get_all_loan()
|
|
1458
|
-
names = [item["Properties"]["Subject"] for item in loan_resp_dict["_embedded"]["AllCasesList"]]
|
|
1459
|
-
if loan_for_business in names:
|
|
1460
|
-
logger.info("Customer record Loan_for_business exists")
|
|
1461
|
-
else:
|
|
1462
|
-
logger.info("Creating customer Record with Loan_for_business ")
|
|
1463
|
-
response_dict = self.create_loan(
|
|
1464
|
-
loan_for_business,
|
|
1465
|
-
loan_for_business,
|
|
1466
|
-
1,
|
|
1467
|
-
2,
|
|
1468
|
-
category_resp_dict[0],
|
|
1469
|
-
stl,
|
|
1470
|
-
prioity_list[0],
|
|
1471
|
-
case_type_list[0],
|
|
1472
|
-
customer_list[0],
|
|
1473
|
-
)
|
|
2368
|
+
return self.do_request(
|
|
2369
|
+
url=request_url,
|
|
2370
|
+
method="GET",
|
|
2371
|
+
headers=REQUEST_HEADERS_JSON,
|
|
2372
|
+
cookies=self.cookie(),
|
|
2373
|
+
timeout=REQUEST_TIMEOUT,
|
|
2374
|
+
failure_message="Request to get categories failed",
|
|
2375
|
+
)
|
|
1474
2376
|
|
|
1475
|
-
|
|
1476
|
-
logger.info("Customer record Loan_for_Corporate_Business exists")
|
|
1477
|
-
else:
|
|
1478
|
-
logger.info("Creating customer Record with Loan_for_Corporate_Business ")
|
|
1479
|
-
response_dict = self.create_loan(
|
|
1480
|
-
loan_for_corporate_business,
|
|
1481
|
-
loan_for_corporate_business,
|
|
1482
|
-
1,
|
|
1483
|
-
2,
|
|
1484
|
-
category_resp_dict[1],
|
|
1485
|
-
ltl,
|
|
1486
|
-
prioity_list[1],
|
|
1487
|
-
case_type_list[1],
|
|
1488
|
-
customer_list[1],
|
|
1489
|
-
)
|
|
2377
|
+
# end method definition
|
|
1490
2378
|
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
else:
|
|
1494
|
-
logger.info("Creating customer Record with loan_for_business_loan_request")
|
|
1495
|
-
response_dict = self.create_loan(
|
|
1496
|
-
loan_for_business_loan_request,
|
|
1497
|
-
loan_for_business_loan_request,
|
|
1498
|
-
1,
|
|
1499
|
-
2,
|
|
1500
|
-
category_resp_dict[2],
|
|
1501
|
-
fl,
|
|
1502
|
-
prioity_list[2],
|
|
1503
|
-
case_type_list[2],
|
|
1504
|
-
customer_list[2],
|
|
1505
|
-
)
|
|
1506
|
-
logger.debug(" RUNTIME -->> loan instance creation ended")
|
|
2379
|
+
def get_category_by_name(self, name: str) -> dict | None:
|
|
2380
|
+
"""Get category entity instance by its name.
|
|
1507
2381
|
|
|
1508
|
-
|
|
2382
|
+
The category ID is only provided by the 'href' in '_links' / 'item'.
|
|
1509
2383
|
|
|
1510
|
-
def get_category_lists(self) -> list:
|
|
1511
|
-
"""get All category entty instances id's
|
|
1512
2384
|
Args:
|
|
1513
|
-
|
|
2385
|
+
name (str):
|
|
2386
|
+
The name of the category.
|
|
2387
|
+
|
|
1514
2388
|
Returns:
|
|
1515
|
-
|
|
2389
|
+
dict | None:
|
|
2390
|
+
Returns the category item or None if a category with the given name does not exist.
|
|
2391
|
+
|
|
2392
|
+
Example:
|
|
2393
|
+
{
|
|
2394
|
+
'_links': {
|
|
2395
|
+
'item': {
|
|
2396
|
+
'href': '/OpentextCaseManagement/entities/Category/items/327681'
|
|
2397
|
+
}
|
|
2398
|
+
},
|
|
2399
|
+
'Properties': {
|
|
2400
|
+
'Name': 'Test 1',
|
|
2401
|
+
'Description': 'Test 1 Description',
|
|
2402
|
+
'CasePrefix': 'TEST',
|
|
2403
|
+
'Status': 1
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
|
|
1516
2407
|
"""
|
|
1517
2408
|
|
|
1518
|
-
|
|
1519
|
-
categoy_resp_dict = self.get_all_categories()
|
|
1520
|
-
for item in categoy_resp_dict["_embedded"]["CategoryList"]:
|
|
1521
|
-
first_item_href = item["_links"]["item"]["href"]
|
|
1522
|
-
integer_value = int(re.search(r'\d+', first_item_href).group())
|
|
1523
|
-
logger.info("Category created with ID -> %d", integer_value)
|
|
1524
|
-
category_resp_dict.append(integer_value)
|
|
1525
|
-
logger.info("All extracted category IDs -> %s", category_resp_dict)
|
|
2409
|
+
categories = self.get_categories()
|
|
1526
2410
|
|
|
1527
|
-
return
|
|
2411
|
+
return self.get_result_item(response=categories, entity_type="CategoryList", key="Name", value=name)
|
|
1528
2412
|
|
|
1529
2413
|
# end method definition
|
|
1530
2414
|
|
|
1531
|
-
def
|
|
1532
|
-
"""Get All
|
|
2415
|
+
def get_category_ids(self) -> list:
|
|
2416
|
+
"""Get All category entity instances IDs.
|
|
2417
|
+
|
|
1533
2418
|
Args:
|
|
1534
2419
|
None
|
|
1535
2420
|
Returns:
|
|
1536
|
-
list: list
|
|
2421
|
+
list: list of category IDs
|
|
2422
|
+
|
|
1537
2423
|
"""
|
|
1538
2424
|
|
|
1539
|
-
|
|
1540
|
-
casetype_resp_dict = self.get_all_case_type()
|
|
1541
|
-
for item in casetype_resp_dict["_embedded"]["AllCaseTypes"]:
|
|
1542
|
-
first_item_href = item["_links"]["item"]["href"]
|
|
1543
|
-
integer_value = int(re.search(r'\d+', first_item_href).group())
|
|
1544
|
-
logger.info("Case type created with ID -> %d", integer_value)
|
|
1545
|
-
case_type_list.append(integer_value)
|
|
1546
|
-
logger.info("All extracted case type IDs -> %s", case_type_list)
|
|
2425
|
+
categories = self.get_categories()
|
|
1547
2426
|
|
|
1548
|
-
return
|
|
2427
|
+
return self.get_result_values(response=categories, entity_type="CategoryList", key="id") or []
|
|
1549
2428
|
|
|
1550
2429
|
# end method definition
|
|
1551
2430
|
|
|
1552
|
-
def
|
|
1553
|
-
|
|
2431
|
+
def create_sub_category(
|
|
2432
|
+
self,
|
|
2433
|
+
parent_id: int,
|
|
2434
|
+
name: str,
|
|
2435
|
+
description: str = "",
|
|
2436
|
+
status: int = 1,
|
|
2437
|
+
) -> dict | None:
|
|
2438
|
+
"""Create sub categoy entity instances.
|
|
2439
|
+
|
|
1554
2440
|
Args:
|
|
1555
|
-
|
|
2441
|
+
parent_id (int):
|
|
2442
|
+
The parent ID of the category.
|
|
2443
|
+
name (str):
|
|
2444
|
+
The name of the sub-category.
|
|
2445
|
+
description (str, optional):
|
|
2446
|
+
The description for the sub-category.
|
|
2447
|
+
status (int, optional):
|
|
2448
|
+
The status ID. Default is 1.
|
|
2449
|
+
|
|
1556
2450
|
Returns:
|
|
1557
|
-
|
|
2451
|
+
dict:
|
|
2452
|
+
Request response (dictionary) or None if the REST call fails.
|
|
2453
|
+
|
|
1558
2454
|
"""
|
|
1559
2455
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
logger.
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
2456
|
+
# Sanity checks as the parameters come directly from payload:
|
|
2457
|
+
if not name:
|
|
2458
|
+
self.logger.error("Cannot create a sub-category without a name!")
|
|
2459
|
+
return None
|
|
2460
|
+
if not parent_id:
|
|
2461
|
+
self.logger.error("Cannot create a sub-category -> '%s' without a parent category ID!", name)
|
|
2462
|
+
return None
|
|
2463
|
+
|
|
2464
|
+
create_sub_category_data = {
|
|
2465
|
+
"Properties": {"Name": name, "Description": description, "Status": status},
|
|
2466
|
+
}
|
|
2467
|
+
|
|
2468
|
+
request_url = (
|
|
2469
|
+
self.config()["categoryUrl"] + "/items/" + str(parent_id) + "/childEntities/SubCategory?defaultinst_ct=abcd"
|
|
2470
|
+
)
|
|
2471
|
+
|
|
2472
|
+
return self.do_request(
|
|
2473
|
+
url=request_url,
|
|
2474
|
+
method="POST",
|
|
2475
|
+
headers=REQUEST_HEADERS_JSON,
|
|
2476
|
+
cookies=self.cookie(),
|
|
2477
|
+
json_data=create_sub_category_data,
|
|
2478
|
+
timeout=REQUEST_TIMEOUT,
|
|
2479
|
+
failure_message="Request to create sub-category -> '{}' with parent category ID -> {} failed".format(
|
|
2480
|
+
name, parent_id
|
|
2481
|
+
),
|
|
2482
|
+
)
|
|
1569
2483
|
|
|
1570
2484
|
# end method definition
|
|
1571
2485
|
|
|
1572
|
-
def
|
|
1573
|
-
"""
|
|
2486
|
+
def get_sub_categories(self, parent_id: int) -> dict | None:
|
|
2487
|
+
"""Get all sub categeries entity instances.
|
|
2488
|
+
|
|
1574
2489
|
Args:
|
|
1575
|
-
|
|
2490
|
+
parent_id (int):
|
|
2491
|
+
The parent ID of the sub categories.
|
|
2492
|
+
|
|
1576
2493
|
Returns:
|
|
1577
|
-
|
|
2494
|
+
dict | None:
|
|
2495
|
+
Request response (dictionary) or None if the REST call fails.
|
|
2496
|
+
|
|
2497
|
+
Example:
|
|
2498
|
+
{
|
|
2499
|
+
'page': {
|
|
2500
|
+
'skip': 0,
|
|
2501
|
+
'top': 10,
|
|
2502
|
+
'count': 1
|
|
2503
|
+
},
|
|
2504
|
+
'_links': {
|
|
2505
|
+
'self': {...},
|
|
2506
|
+
'first': {...}
|
|
2507
|
+
},
|
|
2508
|
+
'_embedded': {
|
|
2509
|
+
'SubCategory': [
|
|
2510
|
+
{
|
|
2511
|
+
'_links': {...},
|
|
2512
|
+
'Identity': {'Id': '1'},
|
|
2513
|
+
'Properties': {'Name': 'Business', 'Description': 'Business', 'Status': 1},
|
|
2514
|
+
'ParentCategory': {
|
|
2515
|
+
'_links': {
|
|
2516
|
+
'self': {'href': '/OpentextCaseManagement/entities/Category/items/1/childEntities/SubCategory/items/1'}
|
|
2517
|
+
},
|
|
2518
|
+
'Properties': {
|
|
2519
|
+
'CasePrefix': 'LOAN',
|
|
2520
|
+
'Description': 'Short Term Loan',
|
|
2521
|
+
'Name': 'Short Term Loan',
|
|
2522
|
+
'Status': 1
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
]
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
|
|
1578
2530
|
"""
|
|
1579
2531
|
|
|
1580
|
-
|
|
1581
|
-
authenticate_dict = self.get_all_priorities()
|
|
1582
|
-
for item in authenticate_dict["_embedded"]["PriorityList"]:
|
|
1583
|
-
first_item_href = item["_links"]["item"]["href"]
|
|
1584
|
-
integer_value = int(re.search(r'\d+', first_item_href).group())
|
|
1585
|
-
logger.info("Priority created with ID -> %d", integer_value)
|
|
1586
|
-
prioity_list.append(integer_value)
|
|
1587
|
-
logger.info("All extracted priority IDs -> %s ", prioity_list)
|
|
2532
|
+
request_url = self.config()["categoryUrl"] + "/items/" + str(parent_id) + "/childEntities/SubCategory"
|
|
1588
2533
|
|
|
1589
|
-
return
|
|
2534
|
+
return self.do_request(
|
|
2535
|
+
url=request_url,
|
|
2536
|
+
method="GET",
|
|
2537
|
+
headers=REQUEST_HEADERS_JSON,
|
|
2538
|
+
cookies=self.cookie(),
|
|
2539
|
+
timeout=REQUEST_TIMEOUT,
|
|
2540
|
+
failure_message="Request to get sub-categories for parent category with ID -> {} failed".format(parent_id),
|
|
2541
|
+
)
|
|
1590
2542
|
|
|
1591
2543
|
# end method definition
|
|
1592
2544
|
|
|
1593
|
-
def
|
|
1594
|
-
"""
|
|
2545
|
+
def get_sub_category_by_parent_and_name(self, parent_id: int, name: str) -> dict | None:
|
|
2546
|
+
"""Get sub category entity instance by its name.
|
|
2547
|
+
|
|
1595
2548
|
Args:
|
|
1596
|
-
|
|
2549
|
+
parent_id (int):
|
|
2550
|
+
The ID of the parent category.
|
|
2551
|
+
name (str):
|
|
2552
|
+
The name of the sub category.
|
|
2553
|
+
|
|
1597
2554
|
Returns:
|
|
1598
|
-
|
|
2555
|
+
dict | None:
|
|
2556
|
+
Returns the sub-category item or None if the sub-category with this name
|
|
2557
|
+
does not exist in the parent category with the given ID.
|
|
2558
|
+
|
|
1599
2559
|
"""
|
|
1600
2560
|
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
if name in names:
|
|
1604
|
-
logger.info("Category record -> '%s' already exists", name)
|
|
1605
|
-
return True
|
|
1606
|
-
logger.info("Creating category record -> '%s'", name)
|
|
2561
|
+
# Get all sub-categories under a given category provided by the parent ID:
|
|
2562
|
+
sub_categories = self.get_sub_categories(parent_id=parent_id)
|
|
1607
2563
|
|
|
1608
|
-
return
|
|
2564
|
+
return self.get_result_item(response=sub_categories, entity_type="SubCategory", key="Name", value=name)
|
|
1609
2565
|
|
|
1610
2566
|
# end method definition
|
|
1611
2567
|
|
|
1612
|
-
def
|
|
1613
|
-
"""
|
|
2568
|
+
def get_sub_category_id(self, parent_id: int, name: str) -> int | None:
|
|
2569
|
+
"""Get the sub category entity instance ID.
|
|
2570
|
+
|
|
1614
2571
|
Args:
|
|
1615
|
-
|
|
2572
|
+
parent_id (int):
|
|
2573
|
+
ID of the parent category.
|
|
2574
|
+
name (str):
|
|
2575
|
+
The name of the sub-category.
|
|
2576
|
+
|
|
1616
2577
|
Returns:
|
|
1617
|
-
|
|
2578
|
+
int | None:
|
|
2579
|
+
Returns the sub-category ID if it exists with the given name in a given parent category.
|
|
2580
|
+
Else returns None.
|
|
2581
|
+
|
|
1618
2582
|
"""
|
|
1619
2583
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
logger.info("Case type record -> '%s' already exists", name)
|
|
1624
|
-
return True
|
|
1625
|
-
logger.info("Creating case type record -> '%s'", name)
|
|
2584
|
+
sub_cat = self.get_sub_category_by_parent_and_name(parent_id=parent_id, name=name)
|
|
2585
|
+
if not sub_cat or "Identity" not in sub_cat:
|
|
2586
|
+
return None
|
|
1626
2587
|
|
|
1627
|
-
return
|
|
2588
|
+
return sub_cat["Identity"].get("Id")
|
|
1628
2589
|
|
|
1629
2590
|
# end method definition
|
|
1630
2591
|
|
|
1631
|
-
def
|
|
1632
|
-
|
|
2592
|
+
def create_case(
|
|
2593
|
+
self,
|
|
2594
|
+
subject: str,
|
|
2595
|
+
description: str,
|
|
2596
|
+
loan_amount: str,
|
|
2597
|
+
loan_duration_in_months: str,
|
|
2598
|
+
category_id: str,
|
|
2599
|
+
sub_category_id: str,
|
|
2600
|
+
priority_id: str,
|
|
2601
|
+
case_type_id: str,
|
|
2602
|
+
customer_id: str,
|
|
2603
|
+
) -> dict | None:
|
|
2604
|
+
"""Create a case entity instance.
|
|
2605
|
+
|
|
2606
|
+
The category, priority, case type and customer entities are
|
|
2607
|
+
referred to with their IDs. These entities need to be created
|
|
2608
|
+
beforehand.
|
|
2609
|
+
|
|
2610
|
+
TODO: This is currently hard-coded for loan cases. Need to be more generic.
|
|
2611
|
+
|
|
1633
2612
|
Args:
|
|
1634
|
-
|
|
2613
|
+
subject (str):
|
|
2614
|
+
The subject of the case.
|
|
2615
|
+
description (str):
|
|
2616
|
+
The description of the case.
|
|
2617
|
+
loan_amount (str):
|
|
2618
|
+
The loan amount of the case.
|
|
2619
|
+
loan_duration_in_months (str):
|
|
2620
|
+
The loan duration of the case (in number of months).
|
|
2621
|
+
category_id (str):
|
|
2622
|
+
The category ID of the case.
|
|
2623
|
+
sub_category_id (str):
|
|
2624
|
+
The sub-category ID of the case.
|
|
2625
|
+
priority_id (str):
|
|
2626
|
+
The priority ID of the case.
|
|
2627
|
+
case_type_id (str):
|
|
2628
|
+
The case type (service) ID of the case.
|
|
2629
|
+
customer_id (str):
|
|
2630
|
+
The ID of the customer for the case.
|
|
2631
|
+
|
|
1635
2632
|
Returns:
|
|
1636
|
-
|
|
2633
|
+
dict | None:
|
|
2634
|
+
Request response (dictionary) or None if the REST call fails
|
|
2635
|
+
|
|
1637
2636
|
"""
|
|
1638
|
-
customer_resp_dict = self.get_all_customers()
|
|
1639
|
-
names = [item["Properties"]["CustomerName"] for item in customer_resp_dict["_embedded"]["CustomerList"]]
|
|
1640
|
-
if name in names:
|
|
1641
|
-
logger.info("Customer -> '%s' already exists", name)
|
|
1642
|
-
return True
|
|
1643
|
-
logger.info("Creating customer -> '%s'", name)
|
|
1644
|
-
return False
|
|
1645
2637
|
|
|
1646
|
-
|
|
2638
|
+
# Validation of parameters:
|
|
2639
|
+
required_fields = {
|
|
2640
|
+
"subject": subject,
|
|
2641
|
+
"category ID": category_id,
|
|
2642
|
+
"sub-category ID": sub_category_id,
|
|
2643
|
+
"priority ID": priority_id,
|
|
2644
|
+
"case type ID": case_type_id,
|
|
2645
|
+
"customer ID": customer_id,
|
|
2646
|
+
}
|
|
1647
2647
|
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
2648
|
+
for name, value in required_fields.items():
|
|
2649
|
+
if not value:
|
|
2650
|
+
self.logger.error("Cannot create a case without a %s!", name)
|
|
2651
|
+
return None
|
|
2652
|
+
|
|
2653
|
+
create_case_data = f"""
|
|
2654
|
+
<SOAP:Envelope xmlns:SOAP=\"http://schemas.xmlsoap.org/soap/envelope/\">
|
|
2655
|
+
<SOAP:Body>
|
|
2656
|
+
<CreateCase xmlns=\"http://schemas/OpentextCaseManagement/Case/operations\">
|
|
2657
|
+
<ns0:Case-create xmlns:ns0=\"http://schemas/OpentextCaseManagement/Case\">
|
|
2658
|
+
<ns0:Subject>{subject}</ns0:Subject>
|
|
2659
|
+
<ns0:Description>{description}</ns0:Description>
|
|
2660
|
+
<ns0:LoanAmount>{loan_amount}</ns0:LoanAmount>
|
|
2661
|
+
<ns0:LoanDurationInMonths>{loan_duration_in_months}</ns0:LoanDurationInMonths>
|
|
2662
|
+
<ns0:CaseType>
|
|
2663
|
+
<ns1:CaseType-id xmlns:ns1=\"http://schemas/OpentextCaseManagement/CaseType\">
|
|
2664
|
+
<ns1:Id>{case_type_id}</ns1:Id>
|
|
2665
|
+
</ns1:CaseType-id>
|
|
2666
|
+
</ns0:CaseType>
|
|
2667
|
+
<ns0:Category>
|
|
2668
|
+
<ns2:Category-id xmlns:ns2=\"http://schemas/OpentextCaseManagement/Category\">
|
|
2669
|
+
<ns2:Id>{category_id}</ns2:Id>
|
|
2670
|
+
</ns2:Category-id>
|
|
2671
|
+
</ns0:Category>
|
|
2672
|
+
<ns0:SubCategory>
|
|
2673
|
+
<ns5:SubCategory-id xmlns:ns5=\"http://schemas/OpentextCaseManagement/Category.SubCategory\">
|
|
2674
|
+
<ns5:Id>{category_id}</ns5:Id>
|
|
2675
|
+
<ns5:Id1>{sub_category_id}</ns5:Id1>
|
|
2676
|
+
</ns5:SubCategory-id>
|
|
2677
|
+
</ns0:SubCategory>
|
|
2678
|
+
<ns0:Priority>
|
|
2679
|
+
<ns3:Priority-id xmlns:ns3=\"http://schemas/OpentextCaseManagement/Priority\">
|
|
2680
|
+
<ns3:Id>{priority_id}</ns3:Id>
|
|
2681
|
+
</ns3:Priority-id>
|
|
2682
|
+
</ns0:Priority>
|
|
2683
|
+
<ns0:ToCustomer>
|
|
2684
|
+
<ns9:Customer-id xmlns:ns9=\"http://schemas/OpentextCaseManagement/Customer\">
|
|
2685
|
+
<ns9:Id>{customer_id}</ns9:Id>
|
|
2686
|
+
</ns9:Customer-id>
|
|
2687
|
+
</ns0:ToCustomer>
|
|
2688
|
+
</ns0:Case-create>
|
|
2689
|
+
</CreateCase>
|
|
2690
|
+
</SOAP:Body>
|
|
2691
|
+
</SOAP:Envelope>
|
|
1654
2692
|
"""
|
|
1655
2693
|
|
|
1656
|
-
|
|
1657
|
-
names = [item["Properties"]["Name"] for item in authenticate_dict["_embedded"]["PriorityList"]]
|
|
1658
|
-
if name in names:
|
|
1659
|
-
logger.info("Priority -> '%s' already exists", name)
|
|
1660
|
-
return True
|
|
1661
|
-
logger.info("Creating priority -> '%s'", name)
|
|
2694
|
+
request_url = self.gateway_url()
|
|
1662
2695
|
|
|
1663
|
-
|
|
2696
|
+
self.logger.debug(
|
|
2697
|
+
"Create case with subject -> '%s'; calling -> '%s'",
|
|
2698
|
+
subject,
|
|
2699
|
+
request_url,
|
|
2700
|
+
)
|
|
2701
|
+
|
|
2702
|
+
retries = 0
|
|
2703
|
+
while True:
|
|
2704
|
+
try:
|
|
2705
|
+
response = requests.post(
|
|
2706
|
+
url=request_url,
|
|
2707
|
+
data=create_case_data,
|
|
2708
|
+
headers=REQUEST_HEADERS_XML,
|
|
2709
|
+
cookies=self.cookie(),
|
|
2710
|
+
timeout=REQUEST_TIMEOUT,
|
|
2711
|
+
)
|
|
2712
|
+
except requests.RequestException as req_exception:
|
|
2713
|
+
self.logger.error(
|
|
2714
|
+
"Request to create case with subject -> '%s' failed with error -> %s",
|
|
2715
|
+
subject,
|
|
2716
|
+
str(req_exception),
|
|
2717
|
+
)
|
|
2718
|
+
return None
|
|
2719
|
+
|
|
2720
|
+
if response.ok:
|
|
2721
|
+
return self.parse_xml(response.text)
|
|
2722
|
+
elif response.status_code == 401 and retries == 0:
|
|
2723
|
+
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
2724
|
+
self.authenticate(revalidate=True)
|
|
2725
|
+
retries += 1
|
|
2726
|
+
else:
|
|
2727
|
+
self.logger.error(
|
|
2728
|
+
"Failed to create case with subject -> '%s' for customer with ID -> '%s' with error -> '%s' when calling -> %s!",
|
|
2729
|
+
subject,
|
|
2730
|
+
customer_id,
|
|
2731
|
+
self.get_soap_element(soap_response=response.text, soap_tag="faultstring"),
|
|
2732
|
+
self.get_soap_element(soap_response=response.text, soap_tag="faultactor"),
|
|
2733
|
+
)
|
|
2734
|
+
self.logger.debug("SOAP message -> %s", response.text)
|
|
2735
|
+
return None
|
|
1664
2736
|
|
|
1665
2737
|
# end method definition
|
|
1666
2738
|
|
|
1667
|
-
def
|
|
1668
|
-
"""
|
|
2739
|
+
def get_cases(self) -> dict | None:
|
|
2740
|
+
"""Get all case entity instances.
|
|
2741
|
+
|
|
1669
2742
|
Args:
|
|
1670
|
-
|
|
2743
|
+
None
|
|
2744
|
+
|
|
1671
2745
|
Returns:
|
|
1672
|
-
|
|
2746
|
+
dict:
|
|
2747
|
+
Request response (dictionary) or None if the REST call fails.
|
|
2748
|
+
|
|
1673
2749
|
"""
|
|
1674
2750
|
|
|
1675
|
-
|
|
1676
|
-
names = [item["Properties"]["Name"] for item in subcategoy_resp_dict["_embedded"]["SubCategory"]]
|
|
1677
|
-
stl=0
|
|
1678
|
-
if name in names:
|
|
1679
|
-
logger.info("Sub category -> '%s' already exists", name)
|
|
1680
|
-
for item in subcategoy_resp_dict["_embedded"]["SubCategory"]:
|
|
1681
|
-
stl = item["Identity"]["Id"]
|
|
1682
|
-
logger.info("Sub category created with ID -> %s", stl)
|
|
1683
|
-
return True
|
|
1684
|
-
logger.info("Creating sub category -> '%s'", name)
|
|
2751
|
+
request_url = self.get_cases_list_url()
|
|
1685
2752
|
|
|
1686
|
-
return
|
|
2753
|
+
return self.do_request(
|
|
2754
|
+
url=request_url,
|
|
2755
|
+
method="GET",
|
|
2756
|
+
headers=REQUEST_HEADERS_JSON,
|
|
2757
|
+
cookies=self.cookie(),
|
|
2758
|
+
timeout=REQUEST_TIMEOUT,
|
|
2759
|
+
failure_message="Request to get cases failed",
|
|
2760
|
+
)
|
|
1687
2761
|
|
|
1688
2762
|
# end method definition
|
|
1689
2763
|
|
|
1690
|
-
def
|
|
1691
|
-
"""
|
|
2764
|
+
def get_case_by_name(self, name: str) -> dict | None:
|
|
2765
|
+
"""Get case instance by its name.
|
|
2766
|
+
|
|
1692
2767
|
Args:
|
|
1693
|
-
name (str):
|
|
2768
|
+
name (str):
|
|
2769
|
+
The name of the case.
|
|
2770
|
+
|
|
1694
2771
|
Returns:
|
|
1695
|
-
|
|
2772
|
+
dict | None:
|
|
2773
|
+
Returns the category item or None if a category with the given name does not exist.
|
|
2774
|
+
|
|
1696
2775
|
"""
|
|
1697
2776
|
|
|
1698
|
-
|
|
1699
|
-
names = [item["Properties"]["Name"] for item in subcategoy_resp_dict["_embedded"]["SubCategory"]]
|
|
1700
|
-
stl=0
|
|
1701
|
-
if name in names:
|
|
1702
|
-
logger.info("Sub category record -> '%s' already exists", name)
|
|
1703
|
-
for item in subcategoy_resp_dict["_embedded"]["SubCategory"]:
|
|
1704
|
-
stl = item["Identity"]["Id"]
|
|
1705
|
-
logger.info("Sub category created with ID -> %s", stl)
|
|
1706
|
-
return stl
|
|
2777
|
+
categories = self.get_cases()
|
|
1707
2778
|
|
|
1708
|
-
return
|
|
2779
|
+
return self.get_result_item(response=categories, entity_type="AllCasesList", key="Name", value=name)
|
|
1709
2780
|
|
|
1710
2781
|
# end method definition
|
|
1711
2782
|
|
|
1712
|
-
def create_users_from_config_file(self, otawpsection: str,
|
|
1713
|
-
"""
|
|
2783
|
+
def create_users_from_config_file(self, otawpsection: str, otds_object: OTDS) -> None:
|
|
2784
|
+
"""Read user information from customizer file and call create user method.
|
|
2785
|
+
|
|
1714
2786
|
Args:
|
|
1715
|
-
otawpsection (str):
|
|
2787
|
+
otawpsection (str):
|
|
2788
|
+
Payload section for AppWorks.
|
|
2789
|
+
otds_object (OTDS):
|
|
2790
|
+
The OTDS object.
|
|
2791
|
+
|
|
1716
2792
|
Returns:
|
|
1717
2793
|
None
|
|
2794
|
+
|
|
1718
2795
|
"""
|
|
1719
2796
|
|
|
1720
2797
|
otds = otawpsection.get("otds", {})
|
|
@@ -1722,7 +2799,7 @@ class OTAWP:
|
|
|
1722
2799
|
users = otds.get("users", [])
|
|
1723
2800
|
if users is not None:
|
|
1724
2801
|
for user in users:
|
|
1725
|
-
|
|
2802
|
+
otds_object.add_user(
|
|
1726
2803
|
user.get("partition"),
|
|
1727
2804
|
user.get("name"),
|
|
1728
2805
|
user.get("description"),
|
|
@@ -1733,31 +2810,37 @@ class OTAWP:
|
|
|
1733
2810
|
roles = otds.get("roles", [])
|
|
1734
2811
|
if roles is not None:
|
|
1735
2812
|
for role in roles:
|
|
1736
|
-
|
|
2813
|
+
otds_object.add_user_to_group(
|
|
1737
2814
|
user.get("name") + "@" + user.get("partition"),
|
|
1738
|
-
# user.get('name'),
|
|
1739
2815
|
role.get("name"),
|
|
1740
2816
|
)
|
|
1741
2817
|
else:
|
|
1742
|
-
logger.
|
|
1743
|
-
"
|
|
2818
|
+
self.logger.warning(
|
|
2819
|
+
"Roles section not in payload for AppWorks users.",
|
|
1744
2820
|
)
|
|
1745
2821
|
else:
|
|
1746
|
-
logger.error(
|
|
1747
|
-
"
|
|
2822
|
+
self.logger.error(
|
|
2823
|
+
"User section not in payload for AppWorks users.",
|
|
1748
2824
|
)
|
|
1749
2825
|
else:
|
|
1750
|
-
logger.error(
|
|
2826
|
+
self.logger.error(
|
|
2827
|
+
"OTDS section not in payload for AppWorks users.",
|
|
2828
|
+
)
|
|
1751
2829
|
|
|
1752
2830
|
# end method definition
|
|
1753
2831
|
|
|
1754
|
-
def create_roles_from_config_file(self, otawpsection: str,
|
|
1755
|
-
"""
|
|
2832
|
+
def create_roles_from_config_file(self, otawpsection: str, otds_object: OTDS) -> None:
|
|
2833
|
+
"""Read grop information from customizer file and call create grop method.
|
|
2834
|
+
|
|
1756
2835
|
Args:
|
|
1757
|
-
otawpsection (str):
|
|
1758
|
-
|
|
2836
|
+
otawpsection (str):
|
|
2837
|
+
Payload section for AppWorks.
|
|
2838
|
+
otds_object (OTDS):
|
|
2839
|
+
The OTDS object used to access the OTDS REST API.
|
|
2840
|
+
|
|
1759
2841
|
Returns:
|
|
1760
2842
|
None
|
|
2843
|
+
|
|
1761
2844
|
"""
|
|
1762
2845
|
|
|
1763
2846
|
otds = otawpsection.get("otds", {})
|
|
@@ -1766,45 +2849,410 @@ class OTAWP:
|
|
|
1766
2849
|
if roles is not None:
|
|
1767
2850
|
for role in roles:
|
|
1768
2851
|
# Add new group if it does not yet exist:
|
|
1769
|
-
if not
|
|
1770
|
-
|
|
2852
|
+
if not otds_object.get_group(group=role.get("name"), show_error=False):
|
|
2853
|
+
otds_object.add_group(
|
|
1771
2854
|
role.get("partition"),
|
|
1772
2855
|
role.get("name"),
|
|
1773
2856
|
role.get("description"),
|
|
1774
2857
|
)
|
|
1775
2858
|
else:
|
|
1776
|
-
logger.error(
|
|
1777
|
-
"
|
|
2859
|
+
self.logger.error(
|
|
2860
|
+
"Roles section not in payload for AppWorks roles/groups.",
|
|
1778
2861
|
)
|
|
1779
2862
|
else:
|
|
1780
|
-
logger.error(
|
|
2863
|
+
self.logger.error(
|
|
2864
|
+
"OTDS section not in payload for AppWorks roles/groups.",
|
|
2865
|
+
)
|
|
1781
2866
|
|
|
1782
2867
|
# end method definition
|
|
1783
2868
|
|
|
1784
|
-
def
|
|
1785
|
-
|
|
2869
|
+
def create_cws_config(
|
|
2870
|
+
self,
|
|
2871
|
+
partition: str,
|
|
2872
|
+
resource_name: str,
|
|
2873
|
+
otcs_url: str,
|
|
2874
|
+
) -> dict | None:
|
|
2875
|
+
"""Create a workspace configuration in CWS.
|
|
2876
|
+
|
|
1786
2877
|
Args:
|
|
1787
|
-
|
|
2878
|
+
partition (str):
|
|
2879
|
+
The partition name for the workspace.
|
|
2880
|
+
resource_name (str):
|
|
2881
|
+
The resource name.
|
|
2882
|
+
otcs_url (str):
|
|
2883
|
+
The OTCS endpoint URL.
|
|
2884
|
+
|
|
1788
2885
|
Returns:
|
|
1789
|
-
None
|
|
2886
|
+
dict | None:
|
|
2887
|
+
Response dictionary if successful, or None if the request fails.
|
|
2888
|
+
|
|
1790
2889
|
"""
|
|
1791
2890
|
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
"
|
|
2891
|
+
# Construct the SOAP request body
|
|
2892
|
+
cws_config_data = f"""
|
|
2893
|
+
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
2894
|
+
<SOAP:Header>
|
|
2895
|
+
<header xmlns="http://schemas.cordys.com/General/1.0/">
|
|
2896
|
+
<Logger/>
|
|
2897
|
+
</header>
|
|
2898
|
+
<i18n:international xmlns:i18n="http://www.w3.org/2005/09/ws-i18n">
|
|
2899
|
+
<i18n:locale>en-US</i18n:locale>
|
|
2900
|
+
</i18n:international>
|
|
2901
|
+
</SOAP:Header>
|
|
2902
|
+
<SOAP:Body>
|
|
2903
|
+
<UpdateXMLObject xmlns="http://schemas.cordys.com/1.0/xmlstore">
|
|
2904
|
+
<tuple lastModified="{int(time.time() * 1000)}"
|
|
2905
|
+
key="/com/ot-ps/csws/otcs_ws_config.xml"
|
|
2906
|
+
level="organization"
|
|
2907
|
+
name="otcs_ws_config.xml"
|
|
2908
|
+
original="/com/ot-ps/csws/otcs_ws_config.xml"
|
|
2909
|
+
version="organization">
|
|
2910
|
+
<new>
|
|
2911
|
+
<CSWSConfig>
|
|
2912
|
+
<Partition>{partition}</Partition>
|
|
2913
|
+
<EndPointUrl>{otcs_url}/cws/services/Authentication</EndPointUrl>
|
|
2914
|
+
<Resources>
|
|
2915
|
+
<Resource type="Cordys">
|
|
2916
|
+
<Name>__OTDS#Shared#Platform#Resource__</Name>
|
|
2917
|
+
<Space>shared</Space>
|
|
2918
|
+
</Resource>
|
|
2919
|
+
<Resource type="OTCS">
|
|
2920
|
+
<Name>{resource_name}</Name>
|
|
2921
|
+
<Space>shared</Space>
|
|
2922
|
+
</Resource>
|
|
2923
|
+
</Resources>
|
|
2924
|
+
</CSWSConfig>
|
|
2925
|
+
</new>
|
|
2926
|
+
</tuple>
|
|
2927
|
+
</UpdateXMLObject>
|
|
2928
|
+
</SOAP:Body>
|
|
2929
|
+
</SOAP:Envelope>
|
|
2930
|
+
"""
|
|
2931
|
+
|
|
2932
|
+
error_messages = [
|
|
2933
|
+
"Collaborative Workspace Service Container is not able to handle the SOAP request",
|
|
2934
|
+
"Service Group Lookup failure",
|
|
2935
|
+
]
|
|
2936
|
+
|
|
2937
|
+
request_url = self.gateway_url()
|
|
2938
|
+
|
|
2939
|
+
self.logger.debug(
|
|
2940
|
+
"Create CWS configuration with partition -> '%s', user -> '%s', and OTCS URL -> '%s'; calling -> '%s'",
|
|
2941
|
+
partition,
|
|
2942
|
+
resource_name,
|
|
2943
|
+
otcs_url,
|
|
2944
|
+
request_url,
|
|
2945
|
+
)
|
|
2946
|
+
|
|
2947
|
+
retries = 0
|
|
2948
|
+
|
|
2949
|
+
while retries < REQUEST_MAX_RETRIES:
|
|
2950
|
+
try:
|
|
2951
|
+
response = requests.post(
|
|
2952
|
+
url=request_url,
|
|
2953
|
+
data=cws_config_data,
|
|
2954
|
+
headers=REQUEST_HEADERS_XML,
|
|
2955
|
+
cookies=self.cookie(),
|
|
2956
|
+
timeout=None,
|
|
2957
|
+
)
|
|
2958
|
+
|
|
2959
|
+
except requests.RequestException as req_exception:
|
|
2960
|
+
self.logger.error(
|
|
2961
|
+
"Request to create CWS config for partition -> '%s' failed with error -> %s. Retry in %d seconds...",
|
|
2962
|
+
partition,
|
|
2963
|
+
str(req_exception),
|
|
2964
|
+
REQUEST_RETRY_DELAY,
|
|
2965
|
+
)
|
|
2966
|
+
retries += 1
|
|
2967
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
2968
|
+
self.logger.info("Retrying... Attempt %d/%d", retries, REQUEST_MAX_RETRIES)
|
|
2969
|
+
continue
|
|
2970
|
+
|
|
2971
|
+
# Handle successful response
|
|
2972
|
+
if response.ok:
|
|
2973
|
+
if any(error_message in response.text for error_message in error_messages):
|
|
2974
|
+
self.logger.warning(
|
|
2975
|
+
"Service error detected, retrying in %d seconds... (Retry %d of %d)",
|
|
2976
|
+
REQUEST_RETRY_DELAY,
|
|
2977
|
+
retries + 1,
|
|
2978
|
+
REQUEST_MAX_RETRIES,
|
|
2979
|
+
)
|
|
2980
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
2981
|
+
retries += 1
|
|
2982
|
+
else:
|
|
2983
|
+
self.logger.info("Successfully created CWS configuration.")
|
|
2984
|
+
return self.parse_xml(response.text)
|
|
2985
|
+
|
|
2986
|
+
# Handle session expiration
|
|
2987
|
+
if response.status_code == 401 and retries == 0:
|
|
2988
|
+
self.logger.warning("Session expired. Re-authenticating...")
|
|
2989
|
+
self.authenticate(revalidate=True)
|
|
2990
|
+
retries += 1
|
|
2991
|
+
continue
|
|
2992
|
+
|
|
2993
|
+
# Handle case where object has been changed by another user:
|
|
2994
|
+
if "Object has been changed by other user" in response.text:
|
|
2995
|
+
self.logger.info("CWS config already exists")
|
|
2996
|
+
self.logger.debug("SOAP message -> %s", response.text)
|
|
2997
|
+
return self.parse_xml(response.text)
|
|
2998
|
+
|
|
2999
|
+
# Log errors for failed requests
|
|
3000
|
+
self.logger.error("Failed to create CWS config; error -> %s", response.text)
|
|
3001
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
3002
|
+
retries += 1
|
|
3003
|
+
# end while retries < REQUEST_MAX_RETRIES:
|
|
3004
|
+
|
|
3005
|
+
# Log when retries are exhausted
|
|
3006
|
+
self.logger.error("Retry limit exceeded. CWS config creation failed.")
|
|
3007
|
+
return None
|
|
3008
|
+
|
|
3009
|
+
# end method definition
|
|
3010
|
+
|
|
3011
|
+
def verify_user_having_role(self, organization: str, user_name: str, role_name: str) -> bool:
|
|
3012
|
+
"""Verify that the user has the specified role.
|
|
3013
|
+
|
|
3014
|
+
Args:
|
|
3015
|
+
organization (str):
|
|
3016
|
+
The organization name.
|
|
3017
|
+
user_name (str):
|
|
3018
|
+
The username to verify.
|
|
3019
|
+
role_name (str):
|
|
3020
|
+
The role to check for the user.
|
|
3021
|
+
|
|
3022
|
+
Returns:
|
|
3023
|
+
bool:
|
|
3024
|
+
True if the user has the role, False if not, or None if request fails.
|
|
3025
|
+
|
|
3026
|
+
"""
|
|
3027
|
+
|
|
3028
|
+
self.logger.info(
|
|
3029
|
+
"Verify user -> '%s' has role -> '%s' in organization -> '%s'...", user_name, role_name, organization
|
|
3030
|
+
)
|
|
3031
|
+
|
|
3032
|
+
# Construct the SOAP request body
|
|
3033
|
+
user_role_data = f"""
|
|
3034
|
+
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
3035
|
+
<SOAP:Header xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
3036
|
+
<header xmlns="http://schemas.cordys.com/General/1.0/">
|
|
3037
|
+
<Logger xmlns="http://schemas.cordys.com/General/1.0/"/>
|
|
3038
|
+
</header>
|
|
3039
|
+
<i18n:international xmlns:i18n="http://www.w3.org/2005/09/ws-i18n">
|
|
3040
|
+
<i18n:locale>en-US</i18n:locale>
|
|
3041
|
+
</i18n:international>
|
|
3042
|
+
</SOAP:Header>
|
|
3043
|
+
<SOAP:Body>
|
|
3044
|
+
<SearchLDAP xmlns:xfr="http://schemas.cordys.com/1.0/xforms/runtime" xmlns="http://schemas.cordys.com/1.0/ldap">
|
|
3045
|
+
<dn xmlns="http://schemas.cordys.com/1.0/ldap">cn=organizational users,o={organization},cn=cordys,cn=defaultInst,o=opentext.net</dn>
|
|
3046
|
+
<scope xmlns="http://schemas.cordys.com/1.0/ldap">1</scope>
|
|
3047
|
+
<filter xmlns="http://schemas.cordys.com/1.0/ldap">&(objectclass=busorganizationaluser)(&(!(cn=SYSTEM))(!(cn=anonymous))(!(cn=wcpLicUser)))(|(description=*{user_name}*)(&(!(description=*))(cn=*{user_name}*)))</filter>
|
|
3048
|
+
<sort xmlns="http://schemas.cordys.com/1.0/ldap">ascending</sort>
|
|
3049
|
+
<sortBy xmlns="http://schemas.cordys.com/1.0/ldap"/>
|
|
3050
|
+
<returnValues xmlns="http://schemas.cordys.com/1.0/ldap">false</returnValues>
|
|
3051
|
+
<return xmlns="http://schemas.cordys.com/1.0/ldap"/>
|
|
3052
|
+
</SearchLDAP>
|
|
3053
|
+
</SOAP:Body>
|
|
3054
|
+
</SOAP:Envelope>
|
|
3055
|
+
"""
|
|
3056
|
+
|
|
3057
|
+
retries = 0
|
|
3058
|
+
|
|
3059
|
+
while retries < REQUEST_MAX_RETRIES:
|
|
3060
|
+
try:
|
|
3061
|
+
response = requests.post(
|
|
3062
|
+
url=self.gateway_url(),
|
|
3063
|
+
data=user_role_data,
|
|
3064
|
+
headers=REQUEST_HEADERS_XML,
|
|
3065
|
+
cookies=self.cookie(),
|
|
3066
|
+
timeout=None,
|
|
1806
3067
|
)
|
|
1807
|
-
|
|
1808
|
-
|
|
3068
|
+
|
|
3069
|
+
except requests.RequestException:
|
|
3070
|
+
self.logger.error(
|
|
3071
|
+
"Request failed during verification of user -> '%s' for role -> '%s'. Retry in %d seconds...",
|
|
3072
|
+
user_name,
|
|
3073
|
+
role_name,
|
|
3074
|
+
REQUEST_RETRY_DELAY,
|
|
3075
|
+
)
|
|
3076
|
+
retries += 1
|
|
3077
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
3078
|
+
self.logger.info("Retrying... Attempt %d/%d", retries, REQUEST_MAX_RETRIES)
|
|
3079
|
+
continue
|
|
3080
|
+
|
|
3081
|
+
# Handle successful response
|
|
3082
|
+
if response.ok:
|
|
3083
|
+
if role_name in response.text: # Corrected syntax for checking if 'Developer' is in the response text
|
|
3084
|
+
self.logger.info("Verified user -> '%s' already has the role -> '%s'.", user_name, role_name)
|
|
3085
|
+
return True # Assuming the user has the role if the response contains 'Developer'
|
|
3086
|
+
else:
|
|
3087
|
+
self.logger.info("Verified user -> '%s' does not yet have role -> '%s'.", user_name, role_name)
|
|
3088
|
+
return False
|
|
3089
|
+
|
|
3090
|
+
# Handle session expiration
|
|
3091
|
+
if response.status_code == 401 and retries == 0:
|
|
3092
|
+
self.logger.warning("Session expired. Re-authenticating...")
|
|
3093
|
+
self.authenticate(revalidate=True)
|
|
3094
|
+
retries += 1
|
|
3095
|
+
continue
|
|
3096
|
+
|
|
3097
|
+
# Log errors for failed requests
|
|
3098
|
+
self.logger.error(
|
|
3099
|
+
"Failed to verify that user -> '%s' has role -> '%s'; error -> %s",
|
|
3100
|
+
user_name,
|
|
3101
|
+
role_name,
|
|
3102
|
+
response.text,
|
|
3103
|
+
)
|
|
3104
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
3105
|
+
retries += 1
|
|
3106
|
+
self.logger.info("Retrying... Attempt %d/%d", retries, REQUEST_MAX_RETRIES)
|
|
3107
|
+
|
|
3108
|
+
# Log when retries are exhausted
|
|
3109
|
+
self.logger.error("Retry limit exceeded. User role verification failed.")
|
|
3110
|
+
|
|
3111
|
+
return False # Return False if the retries limit is exceeded
|
|
1809
3112
|
|
|
1810
3113
|
# end method definition
|
|
3114
|
+
|
|
3115
|
+
def assign_role_to_user(self, organization: str, user_name: str, role_name: str) -> bool:
|
|
3116
|
+
"""Assign a role to a user and verify the role assignment.
|
|
3117
|
+
|
|
3118
|
+
Args:
|
|
3119
|
+
organization (str):
|
|
3120
|
+
The organization name.
|
|
3121
|
+
user_name (str):
|
|
3122
|
+
The username to get the role.
|
|
3123
|
+
role_name (str):
|
|
3124
|
+
The role to be assigned.
|
|
3125
|
+
|
|
3126
|
+
Returns:
|
|
3127
|
+
bool:
|
|
3128
|
+
True if the user received the role, False otherwise.
|
|
3129
|
+
|
|
3130
|
+
"""
|
|
3131
|
+
self.logger.info(
|
|
3132
|
+
"Assign role -> '%s' to user -> '%s' in organization -> '%s'...", role_name, user_name, organization
|
|
3133
|
+
)
|
|
3134
|
+
|
|
3135
|
+
# Check if user already has the role before making the request
|
|
3136
|
+
if self.verify_user_having_role(organization, user_name, role_name):
|
|
3137
|
+
return True
|
|
3138
|
+
|
|
3139
|
+
# Construct the SOAP request body
|
|
3140
|
+
developer_role_data = f"""\
|
|
3141
|
+
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
3142
|
+
<SOAP:Header xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
3143
|
+
<header xmlns="http://schemas.cordys.com/General/1.0/">
|
|
3144
|
+
<Logger xmlns="http://schemas.cordys.com/General/1.0/"/>
|
|
3145
|
+
</header>
|
|
3146
|
+
<i18n:international xmlns:i18n="http://www.w3.org/2005/09/ws-i18n">
|
|
3147
|
+
<i18n:locale>en-US</i18n:locale>
|
|
3148
|
+
</i18n:international>
|
|
3149
|
+
</SOAP:Header>
|
|
3150
|
+
<SOAP:Body>
|
|
3151
|
+
<Update xmlns="http://schemas.cordys.com/1.0/ldap">
|
|
3152
|
+
<tuple>
|
|
3153
|
+
<old>
|
|
3154
|
+
<entry dn="cn=sysadmin,cn=organizational users,o={organization},cn=cordys,cn=defaultInst,o=opentext.net">
|
|
3155
|
+
<role>
|
|
3156
|
+
<string>cn=everyoneIn{organization},cn=organizational roles,o={organization},cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
3157
|
+
<string>cn=Administrator,cn=Cordys@Work,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
3158
|
+
<string>cn=OTDS Push Service,cn=OpenText OTDS Platform Push Connector,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
3159
|
+
</role>
|
|
3160
|
+
<description>
|
|
3161
|
+
<string>{user_name}</string>
|
|
3162
|
+
</description>
|
|
3163
|
+
<cn>
|
|
3164
|
+
<string>{user_name}</string>
|
|
3165
|
+
</cn>
|
|
3166
|
+
<objectclass>
|
|
3167
|
+
<string>top</string>
|
|
3168
|
+
<string>busorganizationalobject</string>
|
|
3169
|
+
<string>busorganizationaluser</string>
|
|
3170
|
+
</objectclass>
|
|
3171
|
+
<authenticationuser>
|
|
3172
|
+
<string>cn={user_name},cn=authenticated users,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
3173
|
+
</authenticationuser>
|
|
3174
|
+
</entry>
|
|
3175
|
+
</old>
|
|
3176
|
+
<new>
|
|
3177
|
+
<entry dn="cn={user_name},cn=organizational users,o={organization},cn=cordys,cn=defaultInst,o=opentext.net">
|
|
3178
|
+
<role>
|
|
3179
|
+
<string>cn=everyoneIn{organization},cn=organizational roles,o={organization},cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
3180
|
+
<string>cn=Administrator,cn=Cordys@Work,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
3181
|
+
<string>cn=OTDS Push Service,cn=OpenText OTDS Platform Push Connector,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
3182
|
+
<string>cn={role_name},cn=Cordys@Work,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
3183
|
+
</role>
|
|
3184
|
+
<description>
|
|
3185
|
+
<string>{user_name}</string>
|
|
3186
|
+
</description>
|
|
3187
|
+
<cn>
|
|
3188
|
+
<string>{user_name}</string>
|
|
3189
|
+
</cn>
|
|
3190
|
+
<objectclass>
|
|
3191
|
+
<string>top</string>
|
|
3192
|
+
<string>busorganizationalobject</string>
|
|
3193
|
+
<string>busorganizationaluser</string>
|
|
3194
|
+
</objectclass>
|
|
3195
|
+
<authenticationuser>
|
|
3196
|
+
<string>cn={user_name},cn=authenticated users,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
3197
|
+
</authenticationuser>
|
|
3198
|
+
</entry>
|
|
3199
|
+
</new>
|
|
3200
|
+
</tuple>
|
|
3201
|
+
</Update>
|
|
3202
|
+
</SOAP:Body>
|
|
3203
|
+
</SOAP:Envelope>
|
|
3204
|
+
"""
|
|
3205
|
+
|
|
3206
|
+
request_url = self.gateway_url()
|
|
3207
|
+
|
|
3208
|
+
self.logger.debug(
|
|
3209
|
+
"Assign role -> '%s' to user -> '%s' in organization -> '%s'; calling -> '%s'",
|
|
3210
|
+
role_name,
|
|
3211
|
+
user_name,
|
|
3212
|
+
organization,
|
|
3213
|
+
request_url,
|
|
3214
|
+
)
|
|
3215
|
+
|
|
3216
|
+
retries = 0
|
|
3217
|
+
|
|
3218
|
+
while retries < REQUEST_MAX_RETRIES:
|
|
3219
|
+
try:
|
|
3220
|
+
response = requests.post(
|
|
3221
|
+
url=request_url,
|
|
3222
|
+
data=developer_role_data,
|
|
3223
|
+
headers=REQUEST_HEADERS_XML,
|
|
3224
|
+
cookies=self.cookie(),
|
|
3225
|
+
timeout=REQUEST_TIMEOUT,
|
|
3226
|
+
)
|
|
3227
|
+
|
|
3228
|
+
if response.ok and role_name in response.text:
|
|
3229
|
+
self.logger.info("Successfully assigned the role -> '%s' to user -> '%s'.", role_name, user_name)
|
|
3230
|
+
return True
|
|
3231
|
+
|
|
3232
|
+
# Handle session expiration
|
|
3233
|
+
if response.status_code == 401 and retries == 0:
|
|
3234
|
+
self.logger.warning("Session expired. Re-authenticating...")
|
|
3235
|
+
self.authenticate(revalidate=True)
|
|
3236
|
+
retries += 1
|
|
3237
|
+
continue # Retry immediately after re-authentication
|
|
3238
|
+
|
|
3239
|
+
# Log failure response
|
|
3240
|
+
self.logger.error(
|
|
3241
|
+
"Failed to assign role -> '%s' to user -> '%s'; error -> %s (%s)",
|
|
3242
|
+
role_name,
|
|
3243
|
+
user_name,
|
|
3244
|
+
response.status_code,
|
|
3245
|
+
response.text,
|
|
3246
|
+
)
|
|
3247
|
+
|
|
3248
|
+
except requests.RequestException as req_exception:
|
|
3249
|
+
self.logger.error("Request failed; error -> %s", str(req_exception))
|
|
3250
|
+
|
|
3251
|
+
retries += 1
|
|
3252
|
+
self.logger.info("Retrying... Attempt %d/%d", retries, REQUEST_MAX_RETRIES)
|
|
3253
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
3254
|
+
|
|
3255
|
+
self.logger.error("Retry limit exceeded. Role assignment failed for user '%s'.", user_name)
|
|
3256
|
+
return False
|
|
3257
|
+
|
|
3258
|
+
# end method definition
|