pyxecm 2.0.0__py3-none-any.whl → 2.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyxecm might be problematic. Click here for more details.
- pyxecm/__init__.py +2 -1
- pyxecm/avts.py +79 -33
- pyxecm/customizer/api/app.py +45 -796
- pyxecm/customizer/api/auth/__init__.py +1 -0
- pyxecm/customizer/api/{auth.py → auth/functions.py} +2 -64
- pyxecm/customizer/api/auth/router.py +78 -0
- pyxecm/customizer/api/common/__init__.py +1 -0
- pyxecm/customizer/api/common/functions.py +47 -0
- pyxecm/customizer/api/{metrics.py → common/metrics.py} +1 -1
- pyxecm/customizer/api/common/models.py +21 -0
- pyxecm/customizer/api/{payload_list.py → common/payload_list.py} +6 -1
- pyxecm/customizer/api/common/router.py +72 -0
- pyxecm/customizer/api/settings.py +25 -0
- pyxecm/customizer/api/terminal/__init__.py +1 -0
- pyxecm/customizer/api/terminal/router.py +87 -0
- pyxecm/customizer/api/v1_csai/__init__.py +1 -0
- pyxecm/customizer/api/v1_csai/router.py +87 -0
- pyxecm/customizer/api/v1_maintenance/__init__.py +1 -0
- pyxecm/customizer/api/v1_maintenance/functions.py +100 -0
- pyxecm/customizer/api/v1_maintenance/models.py +12 -0
- pyxecm/customizer/api/v1_maintenance/router.py +76 -0
- pyxecm/customizer/api/v1_otcs/__init__.py +1 -0
- pyxecm/customizer/api/v1_otcs/functions.py +61 -0
- pyxecm/customizer/api/v1_otcs/router.py +179 -0
- pyxecm/customizer/api/v1_payload/__init__.py +1 -0
- pyxecm/customizer/api/v1_payload/functions.py +179 -0
- pyxecm/customizer/api/v1_payload/models.py +51 -0
- pyxecm/customizer/api/v1_payload/router.py +499 -0
- pyxecm/customizer/browser_automation.py +568 -326
- pyxecm/customizer/customizer.py +204 -430
- pyxecm/customizer/guidewire.py +907 -43
- pyxecm/customizer/k8s.py +243 -56
- pyxecm/customizer/m365.py +104 -15
- pyxecm/customizer/payload.py +1943 -885
- pyxecm/customizer/pht.py +19 -2
- pyxecm/customizer/servicenow.py +22 -5
- pyxecm/customizer/settings.py +9 -6
- pyxecm/helper/xml.py +69 -0
- pyxecm/otac.py +1 -1
- pyxecm/otawp.py +2104 -1535
- pyxecm/otca.py +569 -0
- pyxecm/otcs.py +201 -37
- pyxecm/otds.py +35 -13
- {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/METADATA +6 -29
- pyxecm-2.0.1.dist-info/RECORD +76 -0
- {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/WHEEL +1 -1
- pyxecm-2.0.0.dist-info/RECORD +0 -54
- /pyxecm/customizer/api/{models.py → auth/models.py} +0 -0
- {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/licenses/LICENSE +0 -0
- {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/top_level.txt +0 -0
pyxecm/otawp.py
CHANGED
|
@@ -8,37 +8,63 @@ __email__ = "mdiefenb@opentext.com"
|
|
|
8
8
|
|
|
9
9
|
import json
|
|
10
10
|
import logging
|
|
11
|
+
import platform
|
|
11
12
|
import re
|
|
13
|
+
import sys
|
|
12
14
|
import time
|
|
13
15
|
import uuid
|
|
14
|
-
|
|
16
|
+
from http import HTTPStatus
|
|
17
|
+
from importlib.metadata import version
|
|
15
18
|
|
|
16
19
|
import requests
|
|
17
20
|
|
|
21
|
+
from pyxecm.helper.xml import XML
|
|
18
22
|
from pyxecm.otds import OTDS
|
|
19
23
|
|
|
20
|
-
|
|
24
|
+
APP_NAME = "pyxecm"
|
|
25
|
+
APP_VERSION = version("pyxecm")
|
|
26
|
+
MODULE_NAME = APP_NAME + ".otawp"
|
|
21
27
|
|
|
22
|
-
|
|
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__
|
|
32
|
+
|
|
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,
|
|
23
40
|
"Content-Type": "text/xml; charset=utf-8",
|
|
24
41
|
"accept": "application/xml",
|
|
25
42
|
}
|
|
26
43
|
|
|
27
44
|
REQUEST_FORM_HEADERS = {
|
|
45
|
+
"User-Agent": USER_AGENT,
|
|
28
46
|
"accept": "application/xml;charset=utf-8",
|
|
29
47
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
30
48
|
}
|
|
31
49
|
|
|
32
50
|
REQUEST_HEADERS_JSON = {
|
|
51
|
+
"User-Agent": USER_AGENT,
|
|
33
52
|
"Content-Type": "application/json; charset=utf-8",
|
|
34
53
|
"accept": "application/json",
|
|
35
54
|
}
|
|
55
|
+
|
|
36
56
|
REQUEST_TIMEOUT = 120
|
|
57
|
+
REQUEST_MAX_RETRIES = 10
|
|
58
|
+
REQUEST_RETRY_DELAY = 30
|
|
37
59
|
SYNC_PUBLISH_REQUEST_TIMEOUT = 300
|
|
38
60
|
|
|
61
|
+
default_logger = logging.getLogger(MODULE_NAME)
|
|
62
|
+
|
|
63
|
+
SOAP_FAULT_INDICATOR = "Fault"
|
|
64
|
+
|
|
39
65
|
|
|
40
66
|
class OTAWP:
|
|
41
|
-
"""Class
|
|
67
|
+
"""Class OTAWP is used to automate settings in OpenText AppWorks Platform (OTAWP)."""
|
|
42
68
|
|
|
43
69
|
logger: logging.Logger = default_logger
|
|
44
70
|
|
|
@@ -47,6 +73,241 @@ class OTAWP:
|
|
|
47
73
|
_cookie = None
|
|
48
74
|
_otawp_ticket = None
|
|
49
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
|
+
|
|
50
311
|
def __init__(
|
|
51
312
|
self,
|
|
52
313
|
protocol: str,
|
|
@@ -54,14 +315,9 @@ class OTAWP:
|
|
|
54
315
|
port: int,
|
|
55
316
|
username: str | None = None,
|
|
56
317
|
password: str | None = None,
|
|
318
|
+
organization: str | None = None,
|
|
57
319
|
otawp_ticket: str | None = None,
|
|
58
|
-
otcs_partition_name: str | None = None,
|
|
59
|
-
otds_admin_partition_mame: str | None = None,
|
|
60
320
|
config_map_name: str | None = None,
|
|
61
|
-
otcs_resource_id: str | None = None,
|
|
62
|
-
otds_url: str | None = None,
|
|
63
|
-
otcs_url: str | None = None,
|
|
64
|
-
otcs_base_path: str | None = None,
|
|
65
321
|
license_file: str | None = None,
|
|
66
322
|
product_name: str | None = None,
|
|
67
323
|
product_description: str | None = None,
|
|
@@ -70,23 +326,30 @@ class OTAWP:
|
|
|
70
326
|
"""Initialize OTAWP (AppWorks Platform) object.
|
|
71
327
|
|
|
72
328
|
Args:
|
|
73
|
-
protocol (str):
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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.
|
|
90
353
|
|
|
91
354
|
"""
|
|
92
355
|
|
|
@@ -102,94 +365,32 @@ class OTAWP:
|
|
|
102
365
|
otawp_config["port"] = port if port else 8080
|
|
103
366
|
otawp_config["username"] = username if username else "sysadmin"
|
|
104
367
|
otawp_config["password"] = password if password else ""
|
|
105
|
-
otawp_config["
|
|
106
|
-
otawp_config["
|
|
107
|
-
otawp_config["
|
|
108
|
-
otawp_config["
|
|
109
|
-
otawp_config["
|
|
110
|
-
otawp_config["otcs_url"] = otcs_url if otcs_url else ""
|
|
111
|
-
otawp_config["otcs_base_path"] = otcs_base_path if otcs_base_path else ""
|
|
112
|
-
otawp_config["license_file"] = license_file if license_file else ""
|
|
113
|
-
otawp_config["product_name"] = product_name if product_name else "APPWORKS_PLATFORM"
|
|
114
|
-
otawp_config["product_description"] = (
|
|
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"] = (
|
|
115
373
|
product_description if product_description else "OpenText Appworks Platform"
|
|
116
374
|
)
|
|
117
375
|
|
|
118
376
|
if otawp_ticket:
|
|
377
|
+
self._otawp_ticket = otawp_ticket
|
|
119
378
|
self._cookie = {"defaultinst_SAMLart": otawp_ticket}
|
|
120
379
|
|
|
121
|
-
|
|
380
|
+
server_url = "{}://{}".format(protocol, otawp_config["hostname"])
|
|
122
381
|
if str(port) not in ["80", "443"]:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
otawp_base_url = server + "/home/system"
|
|
126
|
-
|
|
127
|
-
otawp_config["server"] = server if server else "http://appworks"
|
|
128
|
-
|
|
129
|
-
otawp_config["gatewayAuthenticationUrl"] = (
|
|
130
|
-
otawp_base_url
|
|
131
|
-
+ "/com.eibus.web.soap.Gateway.wcp?organization=o=system,cn=cordys,cn=defaultInst,o=opentext.net"
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
otawp_config["soapGatewayUrl"] = (
|
|
135
|
-
otawp_base_url
|
|
136
|
-
+ "/com.eibus.web.soap.Gateway.wcp?organization=o=system,cn=cordys,cn=defaultInst,o=opentext.net&defaultinst_ct=abcd"
|
|
137
|
-
)
|
|
382
|
+
server_url += ":{}".format(port)
|
|
138
383
|
|
|
139
|
-
otawp_config["
|
|
140
|
-
otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Priority?defaultinst_ct=abcd"
|
|
141
|
-
)
|
|
142
|
-
otawp_config["getAllPriorities"] = (
|
|
143
|
-
otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Priority/lists/PriorityList"
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
otawp_config["createCustomer"] = (
|
|
147
|
-
otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Customer?defaultinst_ct=abcd"
|
|
148
|
-
)
|
|
149
|
-
otawp_config["getAllCustomers"] = (
|
|
150
|
-
otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Customer/lists/CustomerList"
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
otawp_config["createCaseType"] = (
|
|
154
|
-
otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/CaseType?defaultinst_ct=abcd"
|
|
155
|
-
)
|
|
156
|
-
otawp_config["getAllCaseTypes"] = (
|
|
157
|
-
otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/CaseType/lists/AllCaseTypes"
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
otawp_config["createCategory"] = (
|
|
161
|
-
otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Category?defaultinst_ct=abcd"
|
|
162
|
-
)
|
|
163
|
-
otawp_config["getAllCategories"] = (
|
|
164
|
-
otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Category/lists/CategoryList"
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
otawp_config["createSource"] = (
|
|
168
|
-
otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Source"
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
otawp_config["getAllSources"] = (
|
|
172
|
-
otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Source/lists/AllSources"
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
otawp_config["getAllSubCategories"] = (
|
|
176
|
-
otawp_base_url
|
|
177
|
-
+ "/app/entityRestService/api/OpentextCaseManagement/entities/Category/childEntities/SubCategory/lists/AllSubcategories"
|
|
178
|
-
)
|
|
384
|
+
otawp_config["serverUrl"] = server_url
|
|
179
385
|
|
|
180
|
-
otawp_config["baseurl"] = otawp_base_url + ""
|
|
181
|
-
otawp_config["createLoan"] = (
|
|
182
|
-
otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Case?defaultinst_ct=abcd"
|
|
183
|
-
)
|
|
184
|
-
otawp_config["getAllLoans"] = (
|
|
185
|
-
otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Case/lists/AllCasesList"
|
|
186
|
-
)
|
|
187
386
|
self._config = otawp_config
|
|
188
387
|
|
|
388
|
+
self.set_organization(otawp_config["organization"])
|
|
389
|
+
|
|
189
390
|
# end method definition
|
|
190
391
|
|
|
191
|
-
def
|
|
192
|
-
"""Return server information.
|
|
392
|
+
def server_url(self) -> str:
|
|
393
|
+
"""Return AppWorks server information.
|
|
193
394
|
|
|
194
395
|
Returns:
|
|
195
396
|
str:
|
|
@@ -202,193 +403,154 @@ class OTAWP:
|
|
|
202
403
|
# end method definition
|
|
203
404
|
|
|
204
405
|
def set_organization(self, organization: str) -> None:
|
|
205
|
-
"""Set the organization context.
|
|
406
|
+
"""Set the AppWorks organization context.
|
|
407
|
+
|
|
408
|
+
This requires to also update all URLs that are including
|
|
409
|
+
the organization.
|
|
206
410
|
|
|
207
411
|
Args:
|
|
208
412
|
organization (str):
|
|
209
|
-
|
|
413
|
+
The AppWorks organization name.
|
|
210
414
|
|
|
211
415
|
"""
|
|
212
416
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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"
|
|
217
443
|
)
|
|
218
444
|
|
|
219
|
-
|
|
220
|
-
self._config["
|
|
445
|
+
self._config["sourceUrl"] = self._config["entityUrl"] + "/Source"
|
|
446
|
+
self._config["sourceListUrl"] = self._config["sourceUrl"] + "/lists/AllSources"
|
|
221
447
|
|
|
222
|
-
self._config["
|
|
448
|
+
self._config["caseUrl"] = self._config["entityUrl"] + "/Case"
|
|
449
|
+
self._config["caseListUrl"] = self._config["caseUrl"] + "/lists/AllCasesList"
|
|
223
450
|
|
|
224
|
-
self.logger.info("
|
|
451
|
+
self.logger.info("AppWorks organization set to -> '%s'.", organization)
|
|
225
452
|
|
|
226
453
|
# end method definition
|
|
227
454
|
|
|
228
|
-
def
|
|
229
|
-
"""Return the
|
|
455
|
+
def base_url(self) -> str:
|
|
456
|
+
"""Return the base URL of AppWorks.
|
|
230
457
|
|
|
231
458
|
Returns:
|
|
232
459
|
str:
|
|
233
|
-
|
|
460
|
+
The base URL of AppWorks Platform.
|
|
234
461
|
|
|
235
462
|
"""
|
|
236
463
|
|
|
237
|
-
return self.config()["
|
|
464
|
+
return self.config()["baseUrl"]
|
|
238
465
|
|
|
239
466
|
# end method definition
|
|
240
467
|
|
|
241
468
|
def license_file(self) -> str:
|
|
242
|
-
"""Return the
|
|
469
|
+
"""Return the AppWorks license file.
|
|
243
470
|
|
|
244
471
|
Returns:
|
|
245
472
|
str:
|
|
246
|
-
|
|
473
|
+
The name (including path) of the AppWorks license file.
|
|
247
474
|
|
|
248
475
|
"""
|
|
249
476
|
|
|
250
|
-
return self.config()["
|
|
477
|
+
return self.config()["licenseFile"]
|
|
251
478
|
|
|
252
479
|
# end method definition
|
|
253
480
|
|
|
254
481
|
def product_name(self) -> str:
|
|
255
|
-
"""Return the
|
|
482
|
+
"""Return the AppWorks product name as used in the OTDS license.
|
|
256
483
|
|
|
257
484
|
Returns:
|
|
258
485
|
str:
|
|
259
|
-
|
|
486
|
+
The AppWorks product name.
|
|
260
487
|
|
|
261
488
|
"""
|
|
262
489
|
|
|
263
|
-
return self.config()["
|
|
490
|
+
return self.config()["productName"]
|
|
264
491
|
|
|
265
492
|
# end method definition
|
|
266
493
|
|
|
267
494
|
def product_description(self) -> str:
|
|
268
|
-
"""Return the
|
|
495
|
+
"""Return the AppWorks product description as used in the OTDS license.
|
|
269
496
|
|
|
270
497
|
Returns:
|
|
271
498
|
str:
|
|
272
|
-
|
|
499
|
+
The AppWorks product description.
|
|
273
500
|
|
|
274
501
|
"""
|
|
275
502
|
|
|
276
|
-
return self.config()["
|
|
503
|
+
return self.config()["productDescription"]
|
|
277
504
|
|
|
278
505
|
# end method definition
|
|
279
506
|
|
|
280
507
|
def hostname(self) -> str:
|
|
281
|
-
"""Return hostname.
|
|
508
|
+
"""Return the AppWorks hostname.
|
|
282
509
|
|
|
283
510
|
Returns:
|
|
284
|
-
str:
|
|
511
|
+
str:
|
|
512
|
+
The AppWorks hostname.
|
|
285
513
|
|
|
286
514
|
"""
|
|
287
515
|
|
|
288
516
|
return self.config()["hostname"]
|
|
289
517
|
|
|
290
518
|
def username(self) -> str:
|
|
291
|
-
"""Return username.
|
|
519
|
+
"""Return the AppWorks username.
|
|
292
520
|
|
|
293
521
|
Returns:
|
|
294
|
-
str:
|
|
522
|
+
str:
|
|
523
|
+
The AppWorks username
|
|
295
524
|
|
|
296
525
|
"""
|
|
526
|
+
|
|
297
527
|
return self.config()["username"]
|
|
298
528
|
|
|
299
529
|
# end method definition
|
|
300
530
|
|
|
301
531
|
def password(self) -> str:
|
|
302
|
-
"""Return password.
|
|
303
|
-
|
|
304
|
-
Returns:
|
|
305
|
-
str: Returns password
|
|
306
|
-
|
|
307
|
-
"""
|
|
308
|
-
return self.config()["password"]
|
|
309
|
-
|
|
310
|
-
# end method definition
|
|
311
|
-
|
|
312
|
-
def otcs_partition_name(self) -> str:
|
|
313
|
-
"""Return OTCS partition name.
|
|
314
|
-
|
|
315
|
-
Returns:
|
|
316
|
-
str: Returns OTCS partition name
|
|
317
|
-
|
|
318
|
-
"""
|
|
319
|
-
return self.config()["otcs_partition_name"]
|
|
320
|
-
|
|
321
|
-
# end method definition
|
|
322
|
-
|
|
323
|
-
def otds_admin_partition_mame(self) -> str:
|
|
324
|
-
"""Return OTDS admin partition name.
|
|
532
|
+
"""Return the AppWorks password.
|
|
325
533
|
|
|
326
534
|
Returns:
|
|
327
535
|
str:
|
|
328
|
-
|
|
536
|
+
The AppWorks password.
|
|
329
537
|
|
|
330
538
|
"""
|
|
331
539
|
|
|
332
|
-
return self.config()["
|
|
540
|
+
return self.config()["password"]
|
|
333
541
|
|
|
334
542
|
# end method definition
|
|
335
543
|
|
|
336
544
|
def config_map_name(self) -> str:
|
|
337
|
-
"""Return config map name.
|
|
338
|
-
|
|
339
|
-
Returns:
|
|
340
|
-
str:
|
|
341
|
-
Returns config map name
|
|
342
|
-
|
|
343
|
-
"""
|
|
344
|
-
return self.config()["config_map_name"]
|
|
345
|
-
|
|
346
|
-
# end method definition
|
|
347
|
-
|
|
348
|
-
def otcs_resource_id(self) -> str:
|
|
349
|
-
"""Return OTCS resource ID.
|
|
350
|
-
|
|
351
|
-
Returns:
|
|
352
|
-
str:
|
|
353
|
-
Returns otcs resource id
|
|
354
|
-
|
|
355
|
-
"""
|
|
356
|
-
return self.config()["otcs_resource_id"]
|
|
357
|
-
|
|
358
|
-
# end method definition
|
|
359
|
-
|
|
360
|
-
def otcs_url(self) -> str:
|
|
361
|
-
"""Return OTCS URL.
|
|
362
|
-
|
|
363
|
-
Returns:
|
|
364
|
-
str:
|
|
365
|
-
Returns the OTCS URL.
|
|
366
|
-
|
|
367
|
-
"""
|
|
368
|
-
return self.config()["otcs_url"]
|
|
369
|
-
|
|
370
|
-
# end method definition
|
|
371
|
-
|
|
372
|
-
def otds_url(self) -> str:
|
|
373
|
-
"""Return the OTDS URL.
|
|
545
|
+
"""Return AppWorks Kubernetes config map name.
|
|
374
546
|
|
|
375
547
|
Returns:
|
|
376
548
|
str:
|
|
377
|
-
|
|
549
|
+
The Kubernetes config map name of AppWorks.
|
|
378
550
|
|
|
379
551
|
"""
|
|
380
|
-
return self.config()["otds_url"]
|
|
381
|
-
|
|
382
|
-
# end method definition
|
|
383
552
|
|
|
384
|
-
|
|
385
|
-
"""Return the OTCS base path.
|
|
386
|
-
|
|
387
|
-
Returns:
|
|
388
|
-
str: Returns otcs base path
|
|
389
|
-
|
|
390
|
-
"""
|
|
391
|
-
return self.config()["otcs_base_path"]
|
|
553
|
+
return self.config()["configMapName"]
|
|
392
554
|
|
|
393
555
|
# end method definition
|
|
394
556
|
|
|
@@ -461,10 +623,11 @@ class OTAWP:
|
|
|
461
623
|
# end method definition
|
|
462
624
|
|
|
463
625
|
def credential_url(self) -> str:
|
|
464
|
-
"""Return the
|
|
626
|
+
"""Return the credentials URL of OTAWP.
|
|
465
627
|
|
|
466
628
|
Returns:
|
|
467
|
-
str:
|
|
629
|
+
str:
|
|
630
|
+
The AppWorks credentials URL.
|
|
468
631
|
|
|
469
632
|
"""
|
|
470
633
|
|
|
@@ -477,7 +640,7 @@ class OTAWP:
|
|
|
477
640
|
|
|
478
641
|
Returns:
|
|
479
642
|
str:
|
|
480
|
-
The SOAP gateway URL.
|
|
643
|
+
The AppWorks SOAP gateway URL.
|
|
481
644
|
|
|
482
645
|
"""
|
|
483
646
|
|
|
@@ -485,32 +648,33 @@ class OTAWP:
|
|
|
485
648
|
|
|
486
649
|
# end method definition
|
|
487
650
|
|
|
488
|
-
def
|
|
651
|
+
def get_create_priority_url(self) -> str:
|
|
489
652
|
"""Return create priority URL of OTAWP.
|
|
490
653
|
|
|
491
654
|
Returns:
|
|
492
|
-
str:
|
|
655
|
+
str:
|
|
656
|
+
The create priority URL.
|
|
493
657
|
|
|
494
658
|
"""
|
|
495
659
|
|
|
496
|
-
return self.config()["
|
|
660
|
+
return self.config()["priorityUrl"] + "?defaultinst_ct=abcd"
|
|
497
661
|
|
|
498
662
|
# end method definition
|
|
499
663
|
|
|
500
|
-
def
|
|
501
|
-
"""
|
|
664
|
+
def get_priorities_list_url(self) -> str:
|
|
665
|
+
"""Get OTAWP URL to retrieve a list of all priorities.
|
|
502
666
|
|
|
503
667
|
Returns:
|
|
504
668
|
str:
|
|
505
|
-
The
|
|
669
|
+
The AppWorks URL to get a list of all priorities.
|
|
506
670
|
|
|
507
671
|
"""
|
|
508
672
|
|
|
509
|
-
return self.config()["
|
|
673
|
+
return self.config()["priorityListUrl"]
|
|
510
674
|
|
|
511
675
|
# end method definition
|
|
512
676
|
|
|
513
|
-
def
|
|
677
|
+
def get_create_customer_url(self) -> str:
|
|
514
678
|
"""Return create customer URL of OTAWP.
|
|
515
679
|
|
|
516
680
|
Returns:
|
|
@@ -519,24 +683,24 @@ class OTAWP:
|
|
|
519
683
|
|
|
520
684
|
"""
|
|
521
685
|
|
|
522
|
-
return self.config()["
|
|
686
|
+
return self.config()["customerUrl"] + "?defaultinst_ct=abcd"
|
|
523
687
|
|
|
524
688
|
# end method definition
|
|
525
689
|
|
|
526
|
-
def
|
|
527
|
-
"""
|
|
690
|
+
def get_customers_list_url(self) -> str:
|
|
691
|
+
"""Get OTAWP URL to retrieve a list of all customers.
|
|
528
692
|
|
|
529
693
|
Returns:
|
|
530
694
|
str:
|
|
531
|
-
The get all customers
|
|
695
|
+
The AppWorks URL to get a list of all customers.
|
|
532
696
|
|
|
533
697
|
"""
|
|
534
698
|
|
|
535
|
-
return self.config()["
|
|
699
|
+
return self.config()["customerListUrl"]
|
|
536
700
|
|
|
537
701
|
# end method definition
|
|
538
702
|
|
|
539
|
-
def
|
|
703
|
+
def get_create_casetype_url(self) -> str:
|
|
540
704
|
"""Return create case type URL of OTAWP.
|
|
541
705
|
|
|
542
706
|
Returns:
|
|
@@ -545,12 +709,12 @@ class OTAWP:
|
|
|
545
709
|
|
|
546
710
|
"""
|
|
547
711
|
|
|
548
|
-
return self.config()["
|
|
712
|
+
return self.config()["caseTypeUrl"] + "?defaultinst_ct=abcd"
|
|
549
713
|
|
|
550
714
|
# end method definition
|
|
551
715
|
|
|
552
|
-
def
|
|
553
|
-
"""
|
|
716
|
+
def get_casetypes_list_url(self) -> str:
|
|
717
|
+
"""Get OTAWP URL to retrieve a list of all case types.
|
|
554
718
|
|
|
555
719
|
Returns:
|
|
556
720
|
str:
|
|
@@ -558,12 +722,12 @@ class OTAWP:
|
|
|
558
722
|
|
|
559
723
|
"""
|
|
560
724
|
|
|
561
|
-
return self.config()["
|
|
725
|
+
return self.config()["caseTypeListUrl"]
|
|
562
726
|
|
|
563
727
|
# end method definition
|
|
564
728
|
|
|
565
|
-
def
|
|
566
|
-
"""
|
|
729
|
+
def get_create_category_url(self) -> str:
|
|
730
|
+
"""Get OTAWP URL to create a category.
|
|
567
731
|
|
|
568
732
|
Returns:
|
|
569
733
|
str:
|
|
@@ -571,12 +735,12 @@ class OTAWP:
|
|
|
571
735
|
|
|
572
736
|
"""
|
|
573
737
|
|
|
574
|
-
return self.config()["
|
|
738
|
+
return self.config()["categoryUrl"] + "?defaultinst_ct=abcd"
|
|
575
739
|
|
|
576
740
|
# end method definition
|
|
577
741
|
|
|
578
|
-
def
|
|
579
|
-
"""
|
|
742
|
+
def get_categories_list_url(self) -> str:
|
|
743
|
+
"""Get OTAWP URL to retrieve a list of all categories.
|
|
580
744
|
|
|
581
745
|
Returns:
|
|
582
746
|
str:
|
|
@@ -584,60 +748,65 @@ class OTAWP:
|
|
|
584
748
|
|
|
585
749
|
"""
|
|
586
750
|
|
|
587
|
-
return self.config()["
|
|
751
|
+
return self.config()["categoryListUrl"]
|
|
588
752
|
|
|
589
753
|
# end method definition
|
|
590
754
|
|
|
591
|
-
def
|
|
592
|
-
"""
|
|
755
|
+
def get_create_case_url(self) -> str:
|
|
756
|
+
"""Get OTAWP URL to create a case (e.g. a loan).
|
|
593
757
|
|
|
594
758
|
Returns:
|
|
595
759
|
str:
|
|
596
|
-
The
|
|
760
|
+
The create case URL.
|
|
597
761
|
|
|
598
762
|
"""
|
|
599
763
|
|
|
600
|
-
return self.config()["
|
|
764
|
+
return self.config()["caseUrl"] + "?defaultinst_ct=abcd"
|
|
601
765
|
|
|
602
766
|
# end method definition
|
|
603
767
|
|
|
604
|
-
def
|
|
605
|
-
"""
|
|
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
|
+
"""
|
|
606
776
|
|
|
607
|
-
return
|
|
777
|
+
return self.config()["caseListUrl"]
|
|
608
778
|
|
|
609
779
|
# end method definition
|
|
610
780
|
|
|
611
781
|
def parse_xml(self, xml_string: str) -> dict:
|
|
612
|
-
"""Parse XML string and return a dictionary without namespaces.
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
}
|
|
622
|
-
return {tag: element.text.strip() if element.text else None}
|
|
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.
|
|
623
791
|
|
|
624
|
-
|
|
792
|
+
"""
|
|
625
793
|
|
|
626
|
-
return
|
|
794
|
+
return XML.xml_to_dict(xml_string=xml_string)
|
|
627
795
|
|
|
628
796
|
# end method definition
|
|
629
797
|
|
|
630
|
-
def find_key(self, data: dict | list, target_key: str) -> str:
|
|
798
|
+
def find_key(self, data: dict | list, target_key: str) -> str | None:
|
|
631
799
|
"""Recursively search for a key in a nested dictionary and return its value.
|
|
632
800
|
|
|
633
801
|
Args:
|
|
634
802
|
data (dict | list):
|
|
635
|
-
|
|
803
|
+
The data structure to find a key in.
|
|
636
804
|
target_key (str):
|
|
637
|
-
|
|
805
|
+
The key to find.
|
|
638
806
|
|
|
639
807
|
Returns:
|
|
640
|
-
|
|
808
|
+
str:
|
|
809
|
+
The value for the key.
|
|
641
810
|
|
|
642
811
|
"""
|
|
643
812
|
|
|
@@ -658,6 +827,157 @@ class OTAWP:
|
|
|
658
827
|
|
|
659
828
|
# end method definition
|
|
660
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
|
+
|
|
661
981
|
def parse_request_response(
|
|
662
982
|
self,
|
|
663
983
|
response_object: object,
|
|
@@ -706,207 +1026,540 @@ class OTAWP:
|
|
|
706
1026
|
else:
|
|
707
1027
|
self.logger.warning(message)
|
|
708
1028
|
return None
|
|
1029
|
+
|
|
709
1030
|
return dict_object
|
|
710
1031
|
|
|
711
1032
|
# end method definition
|
|
712
1033
|
|
|
713
|
-
def
|
|
714
|
-
"""
|
|
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.
|
|
715
1036
|
|
|
716
1037
|
Args:
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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.
|
|
720
1057
|
|
|
721
1058
|
Returns:
|
|
722
|
-
|
|
723
|
-
|
|
1059
|
+
str | None:
|
|
1060
|
+
Value of the entity property with the given key, or None if no value is found.
|
|
724
1061
|
|
|
725
1062
|
"""
|
|
726
1063
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
self.logger.debug(
|
|
730
|
-
"Session still valid - return existing cookie -> %s",
|
|
731
|
-
str(self._cookie),
|
|
732
|
-
)
|
|
733
|
-
return self._cookie
|
|
1064
|
+
if not entity or "Properties" not in entity:
|
|
1065
|
+
return None
|
|
734
1066
|
|
|
735
|
-
|
|
1067
|
+
properties = entity["Properties"]
|
|
736
1068
|
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
response = requests.post(
|
|
741
|
-
url=self.credential_url(),
|
|
742
|
-
data=self.credentials(),
|
|
743
|
-
headers=REQUEST_HEADERS,
|
|
744
|
-
timeout=REQUEST_TIMEOUT,
|
|
745
|
-
)
|
|
746
|
-
except requests.exceptions.RequestException as exception:
|
|
747
|
-
self.logger.warning(
|
|
748
|
-
"Unable to connect to OTAWP authentication endpoint -> %s; error -> %s",
|
|
749
|
-
self.credential_url(),
|
|
750
|
-
str(exception),
|
|
751
|
-
)
|
|
752
|
-
self.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))
|
|
753
1072
|
return None
|
|
754
1073
|
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
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:
|
|
759
1082
|
return None
|
|
760
|
-
|
|
761
|
-
authenticate_dict,
|
|
762
|
-
"AssertionArtifact",
|
|
763
|
-
)
|
|
764
|
-
if isinstance(assertion_artifact_dict, dict):
|
|
765
|
-
otawp_ticket = assertion_artifact_dict.get("AssertionArtifact")
|
|
766
|
-
self.logger.debug("SAML token -> %s", otawp_ticket)
|
|
767
|
-
else:
|
|
768
|
-
self.logger.error(
|
|
769
|
-
"Failed to request an OTAWP ticket; error -> %s",
|
|
770
|
-
response.text,
|
|
771
|
-
)
|
|
772
|
-
return None
|
|
773
|
-
|
|
774
|
-
self._cookie = {"defaultinst_SAMLart": otawp_ticket, "defaultinst_ct": "abcd"}
|
|
775
|
-
self._otawp_ticket = otawp_ticket
|
|
1083
|
+
return int(match.group(1))
|
|
776
1084
|
|
|
777
|
-
return
|
|
1085
|
+
return properties[key]
|
|
778
1086
|
|
|
779
1087
|
# end method definition
|
|
780
1088
|
|
|
781
|
-
def
|
|
782
|
-
|
|
1089
|
+
def get_result_value(
|
|
1090
|
+
self,
|
|
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.
|
|
783
1098
|
|
|
784
1099
|
Args:
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
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.
|
|
789
1112
|
|
|
790
1113
|
Returns:
|
|
791
|
-
|
|
792
|
-
|
|
1114
|
+
str | None:
|
|
1115
|
+
Value of the item with the given key, or None if no value is found.
|
|
793
1116
|
|
|
794
1117
|
"""
|
|
795
1118
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
workspace_name,
|
|
799
|
-
workspace_id,
|
|
800
|
-
)
|
|
801
|
-
unique_id = uuid.uuid4()
|
|
1119
|
+
if not response:
|
|
1120
|
+
return None
|
|
802
1121
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
<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">
|
|
806
|
-
<instance>
|
|
807
|
-
<c:Document s="T" path="D43B04C1-CD0B-A1EB-A898-53C71DB5D652">
|
|
808
|
-
<c:Header>
|
|
809
|
-
<c:System>
|
|
810
|
-
<c:TypeID>001A6B1E-0C0C-11DF-F5E9-866B84E5D671</c:TypeID>
|
|
811
|
-
<c:ID>D43B04C1-CD0B-A1EB-A898-53C71DB5D652</c:ID>
|
|
812
|
-
<c:Name>D43B04C1-CD0B-A1EB-A898-53C71DB5D652</c:Name>
|
|
813
|
-
<c:Description>D43B04C1-CD0B-A1EB-A898-53C71DB5D652</c:Description>
|
|
814
|
-
</c:System>
|
|
815
|
-
</c:Header>
|
|
816
|
-
<c:Content>
|
|
817
|
-
<DevelopmentWorkspaceCreator type="com.cordys.cws.runtime.types.workspace.creation.DevelopmentWorkspaceCreator" runtimeDocumentID="D43B04C1-CD0B-A1EB-A898-53C71DB61652">
|
|
818
|
-
<Workspace>
|
|
819
|
-
<uri id="{workspace_id}"/>
|
|
820
|
-
</Workspace>
|
|
821
|
-
</DevelopmentWorkspaceCreator>
|
|
822
|
-
</c:Content>
|
|
823
|
-
</c:Document>
|
|
824
|
-
</instance>
|
|
825
|
-
<__prefetch>
|
|
826
|
-
<Document xmlns="http://schemas.cordys.com/cws/1.0" path="{workspace_name}" s="N" isLocal="IN_LOCAL">
|
|
827
|
-
<Header>
|
|
828
|
-
<System>
|
|
829
|
-
<ID>{workspace_id}</ID>
|
|
830
|
-
<Name>{workspace_name}</Name>
|
|
831
|
-
<TypeID>{{4CE11E00-2D97-45C0-BC6C-FAEC1D871026}}</TypeID>
|
|
832
|
-
<ParentID/>
|
|
833
|
-
<Description>{workspace_name}</Description>
|
|
834
|
-
<CreatedBy>sysadmin</CreatedBy>
|
|
835
|
-
<CreationDate/>
|
|
836
|
-
<LastModifiedBy>sysadmin</LastModifiedBy>
|
|
837
|
-
<LastModifiedDate>2021-04-21T06:52:34.254</LastModifiedDate>
|
|
838
|
-
<FQN/>
|
|
839
|
-
<Annotation/>
|
|
840
|
-
<ParentID/>
|
|
841
|
-
<OptimisticLock/>
|
|
842
|
-
</System>
|
|
843
|
-
</Header>
|
|
844
|
-
<Content>
|
|
845
|
-
<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">
|
|
846
|
-
<ExternalID/>
|
|
847
|
-
<OrganizationName/>
|
|
848
|
-
<SCMAdapter>
|
|
849
|
-
<uri id="{unique_id}"/>
|
|
850
|
-
</SCMAdapter>
|
|
851
|
-
<UpgradedTo/>
|
|
852
|
-
<LastWorkspaceUpgradeStep/>
|
|
853
|
-
<Metaspace/>
|
|
854
|
-
</DevelopmentWorkspace>
|
|
855
|
-
</Content>
|
|
856
|
-
</Document>
|
|
857
|
-
<Document xmlns="http://schemas.cordys.com/cws/1.0" path="{workspace_name}/Untitled No SCM adapter" s="N" isLocal="IN_LOCAL">
|
|
858
|
-
<Header>
|
|
859
|
-
<System>
|
|
860
|
-
<ID>{unique_id}</ID>
|
|
861
|
-
<Name>Untitled No SCM adapter</Name>
|
|
862
|
-
<TypeID>{{E89F3F62-8CA3-4F93-95A8-F76642FD5124}}</TypeID>
|
|
863
|
-
<ParentID>{workspace_id}</ParentID>
|
|
864
|
-
<Description>Untitled No SCM adapter</Description>
|
|
865
|
-
<CreatedBy>sysadmin</CreatedBy>
|
|
866
|
-
<CreationDate/>
|
|
867
|
-
<LastModifiedBy>sysadmin</LastModifiedBy>
|
|
868
|
-
<LastModifiedDate>2021-04-21T06:52:34.254</LastModifiedDate>
|
|
869
|
-
<FQN/>
|
|
870
|
-
<Annotation/>
|
|
871
|
-
<OptimisticLock/>
|
|
872
|
-
</System>
|
|
873
|
-
</Header>
|
|
874
|
-
<Content>
|
|
875
|
-
<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">
|
|
876
|
-
<Workspace>
|
|
877
|
-
<uri id="{workspace_id}"/>
|
|
878
|
-
</Workspace>
|
|
879
|
-
</NullAdapter>
|
|
880
|
-
</Content>
|
|
881
|
-
</Document>
|
|
882
|
-
</__prefetch>
|
|
883
|
-
</createWorkspace>
|
|
884
|
-
</SOAP:Body>
|
|
885
|
-
</SOAP:Envelope>"""
|
|
1122
|
+
if "_embedded" not in response:
|
|
1123
|
+
return None
|
|
886
1124
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
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),
|
|
895
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,
|
|
1388
|
+
workspace_id,
|
|
1389
|
+
)
|
|
1390
|
+
|
|
1391
|
+
unique_id = uuid.uuid4()
|
|
1392
|
+
|
|
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()
|
|
1489
|
+
|
|
1490
|
+
retries = 0
|
|
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
|
+
|
|
896
1512
|
if response.ok:
|
|
897
1513
|
self.logger.info(
|
|
898
|
-
"Successfully created workspace -> '%s'
|
|
1514
|
+
"Successfully created workspace -> '%s' (%s).",
|
|
899
1515
|
workspace_name,
|
|
900
1516
|
workspace_id,
|
|
901
1517
|
)
|
|
902
|
-
|
|
1518
|
+
# True indicates that a new workspaces has been created.
|
|
1519
|
+
return (self.parse_xml(response.text), True)
|
|
1520
|
+
|
|
903
1521
|
# Check if Session has expired - then re-authenticate and try once more
|
|
904
1522
|
if response.status_code == 401 and retries == 0:
|
|
905
|
-
self.logger.warning("Session
|
|
1523
|
+
self.logger.warning("Session expired. Re-authenticating...")
|
|
906
1524
|
self.authenticate(revalidate=True)
|
|
907
1525
|
retries += 1
|
|
908
|
-
|
|
909
|
-
|
|
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)
|
|
910
1563
|
|
|
911
1564
|
# end method definition
|
|
912
1565
|
|
|
@@ -925,13 +1578,17 @@ class OTAWP:
|
|
|
925
1578
|
|
|
926
1579
|
"""
|
|
927
1580
|
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
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
|
|
1587
|
+
|
|
1588
|
+
self.logger.info("Starting synchronization of workspace -> '%s' (%s)...", workspace_name, workspace_id)
|
|
932
1589
|
|
|
933
1590
|
# SOAP request body
|
|
934
|
-
|
|
1591
|
+
sync_workspace_data = f"""
|
|
935
1592
|
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
936
1593
|
<SOAP:Body>
|
|
937
1594
|
<Synchronize workspaceID="{workspace_id}" xmlns="http://schemas.cordys.com/cws/synchronize/1.0">
|
|
@@ -942,47 +1599,62 @@ class OTAWP:
|
|
|
942
1599
|
</SOAP:Envelope>
|
|
943
1600
|
"""
|
|
944
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
|
+
)
|
|
1610
|
+
|
|
945
1611
|
retries = 0
|
|
946
|
-
max_retries = 6
|
|
947
|
-
retry_delay = 60
|
|
948
1612
|
|
|
949
|
-
while retries <
|
|
1613
|
+
while retries < REQUEST_MAX_RETRIES:
|
|
950
1614
|
try:
|
|
951
1615
|
response = requests.post(
|
|
952
|
-
url=
|
|
953
|
-
data=
|
|
954
|
-
headers=
|
|
1616
|
+
url=request_url,
|
|
1617
|
+
data=sync_workspace_data,
|
|
1618
|
+
headers=REQUEST_HEADERS_XML,
|
|
955
1619
|
cookies=self.cookie(),
|
|
956
1620
|
timeout=SYNC_PUBLISH_REQUEST_TIMEOUT,
|
|
957
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
|
|
958
1632
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
workspace_name,
|
|
963
|
-
)
|
|
964
|
-
return self.parse_xml(response.text)
|
|
1633
|
+
if response.ok:
|
|
1634
|
+
self.logger.info("Successfully synchronized workspace -> '%s' (%s).", workspace_name, workspace_id)
|
|
1635
|
+
return self.parse_xml(response.text)
|
|
965
1636
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1637
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
1638
|
+
if response.status_code == 401 and retries == 0:
|
|
1639
|
+
self.logger.warning("Session expired. Re-authenticating...")
|
|
1640
|
+
self.authenticate(revalidate=True)
|
|
1641
|
+
retries += 1
|
|
1642
|
+
continue
|
|
971
1643
|
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
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
|
|
977
1652
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1653
|
+
self.logger.error("Unexpected error during workspace synchronization -> %s", response.text)
|
|
1654
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
1655
|
+
retries += 1
|
|
981
1656
|
|
|
982
|
-
|
|
983
|
-
self.logger.error("Sync failed with error. Proceeding with retry...")
|
|
984
|
-
time.sleep(retry_delay)
|
|
985
|
-
retries += 1
|
|
1657
|
+
# end while retries < REQUEST_MAX_RETRIES:
|
|
986
1658
|
|
|
987
1659
|
self.logger.error(
|
|
988
1660
|
"Synchronization failed for workspace -> '%s' after %d retries.",
|
|
@@ -996,30 +1668,55 @@ class OTAWP:
|
|
|
996
1668
|
def publish_project(
|
|
997
1669
|
self,
|
|
998
1670
|
workspace_name: str,
|
|
999
|
-
project_name: str,
|
|
1000
1671
|
workspace_id: str,
|
|
1672
|
+
project_name: str,
|
|
1001
1673
|
project_id: str,
|
|
1002
|
-
) ->
|
|
1674
|
+
) -> bool:
|
|
1003
1675
|
"""Publish the workspace project.
|
|
1004
1676
|
|
|
1005
1677
|
Args:
|
|
1006
|
-
workspace_name (str):
|
|
1007
|
-
|
|
1008
|
-
workspace_id (str):
|
|
1009
|
-
|
|
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.
|
|
1010
1686
|
|
|
1011
1687
|
Returns:
|
|
1012
|
-
|
|
1688
|
+
bool:
|
|
1689
|
+
True if successful, False if it fails after retries.
|
|
1013
1690
|
|
|
1014
1691
|
"""
|
|
1015
1692
|
|
|
1016
1693
|
self.logger.info(
|
|
1017
|
-
"Publish project -> '%s' in workspace -> '%s'...",
|
|
1694
|
+
"Publish project -> '%s' (%s) in workspace -> '%s' (%s)...",
|
|
1018
1695
|
project_name,
|
|
1696
|
+
project_id,
|
|
1019
1697
|
workspace_name,
|
|
1698
|
+
workspace_id,
|
|
1020
1699
|
)
|
|
1021
1700
|
|
|
1022
|
-
|
|
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/">
|
|
1023
1720
|
<SOAP:Body>
|
|
1024
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">
|
|
1025
1722
|
<object>
|
|
@@ -1027,62 +1724,72 @@ class OTAWP:
|
|
|
1027
1724
|
</object>
|
|
1028
1725
|
</deployObject>
|
|
1029
1726
|
</SOAP:Body>
|
|
1030
|
-
</SOAP:Envelope>
|
|
1727
|
+
</SOAP:Envelope>
|
|
1728
|
+
"""
|
|
1031
1729
|
|
|
1032
1730
|
# Initialize retry parameters
|
|
1033
|
-
max_retries = 10
|
|
1034
1731
|
retries = 0
|
|
1035
1732
|
success_indicator = "deployObjectResponse"
|
|
1036
1733
|
|
|
1037
|
-
while retries <
|
|
1734
|
+
while retries < REQUEST_MAX_RETRIES:
|
|
1038
1735
|
try:
|
|
1039
1736
|
response = requests.post(
|
|
1040
1737
|
url=self.gateway_url(),
|
|
1041
|
-
data=
|
|
1042
|
-
headers=
|
|
1738
|
+
data=project_publish_data,
|
|
1739
|
+
headers=REQUEST_HEADERS_XML,
|
|
1043
1740
|
cookies=self.cookie(),
|
|
1044
1741
|
timeout=SYNC_PUBLISH_REQUEST_TIMEOUT,
|
|
1045
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
|
|
1046
1754
|
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
"Expected success indicator -> '%s' but it was not found in response. Retrying in 30 seconds... (Attempt %d of %d)",
|
|
1059
|
-
success_indicator,
|
|
1060
|
-
retries + 1,
|
|
1061
|
-
max_retries,
|
|
1062
|
-
)
|
|
1063
|
-
elif response.status_code == 401:
|
|
1064
|
-
# Check for session expiry and retry authentication
|
|
1065
|
-
self.logger.warning("Session has expired - re-authenticating...")
|
|
1066
|
-
self.authenticate(revalidate=True)
|
|
1755
|
+
# Check if the response is successful
|
|
1756
|
+
if response.ok:
|
|
1757
|
+
if success_indicator in response.text:
|
|
1758
|
+
self.logger.info(
|
|
1759
|
+
"Successfully published project -> '%s' (%s) in workspace -> '%s' (%s)",
|
|
1760
|
+
project_name,
|
|
1761
|
+
project_id,
|
|
1762
|
+
workspace_name,
|
|
1763
|
+
workspace_id,
|
|
1764
|
+
)
|
|
1765
|
+
return True
|
|
1067
1766
|
else:
|
|
1068
|
-
self.logger.
|
|
1069
|
-
"
|
|
1070
|
-
|
|
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,
|
|
1071
1770
|
retries + 1,
|
|
1072
|
-
|
|
1771
|
+
REQUEST_MAX_RETRIES,
|
|
1073
1772
|
)
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
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,
|
|
1781
|
+
retries + 1,
|
|
1782
|
+
REQUEST_MAX_RETRIES,
|
|
1783
|
+
)
|
|
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)
|
|
1081
1791
|
|
|
1082
|
-
|
|
1083
|
-
self.logger.error("Sync failed with error. Proceeding with retry...")
|
|
1084
|
-
retries += 1
|
|
1085
|
-
time.sleep(30)
|
|
1792
|
+
# end while retries < REQUEST_MAX_RETRIES:
|
|
1086
1793
|
|
|
1087
1794
|
# After reaching the maximum number of retries, log failure and return False
|
|
1088
1795
|
self.logger.error(
|
|
@@ -1090,376 +1797,185 @@ class OTAWP:
|
|
|
1090
1797
|
project_name,
|
|
1091
1798
|
workspace_name,
|
|
1092
1799
|
)
|
|
1800
|
+
|
|
1093
1801
|
return False
|
|
1094
1802
|
|
|
1095
1803
|
# end method definition
|
|
1096
1804
|
|
|
1097
|
-
def create_priority(self, name: str, description: str, status: int) -> dict | None:
|
|
1098
|
-
"""Create Priority entity
|
|
1805
|
+
def create_priority(self, name: str, description: str = "", status: int = 1) -> dict | None:
|
|
1806
|
+
"""Create Priority entity instance.
|
|
1099
1807
|
|
|
1100
1808
|
Args:
|
|
1101
|
-
name (str):
|
|
1102
|
-
|
|
1103
|
-
|
|
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.
|
|
1104
1815
|
|
|
1105
1816
|
Returns:
|
|
1106
1817
|
dict:
|
|
1107
1818
|
Request response (dictionary) or None if the REST call fails
|
|
1108
1819
|
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
retries = 0
|
|
1114
|
-
while True:
|
|
1115
|
-
response = requests.post(
|
|
1116
|
-
url=self.create_priority_url(),
|
|
1117
|
-
json=create_priority,
|
|
1118
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1119
|
-
cookies=self.cookie(),
|
|
1120
|
-
timeout=REQUEST_TIMEOUT,
|
|
1121
|
-
)
|
|
1122
|
-
if response.ok:
|
|
1123
|
-
self.logger.info("Priority created successfully")
|
|
1124
|
-
return self.parse_request_response(
|
|
1125
|
-
response_object=response,
|
|
1126
|
-
additional_error_message="This can be normal during restart",
|
|
1127
|
-
show_error=False,
|
|
1128
|
-
)
|
|
1129
|
-
if response.status_code == 401 and retries == 0:
|
|
1130
|
-
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
1131
|
-
self.authenticate(revalidate=True)
|
|
1132
|
-
retries += 1
|
|
1133
|
-
self.logger.error(response.text)
|
|
1134
|
-
return None
|
|
1135
|
-
|
|
1136
|
-
# end method definition
|
|
1137
|
-
|
|
1138
|
-
def get_all_priorities(self) -> dict | None:
|
|
1139
|
-
"""Get all priorities from entity.
|
|
1140
|
-
|
|
1141
|
-
Args:
|
|
1142
|
-
None
|
|
1143
|
-
|
|
1144
|
-
Returns:
|
|
1145
|
-
dict:
|
|
1146
|
-
Request response (dictionary) or None if the REST call fails.
|
|
1147
|
-
|
|
1148
|
-
"""
|
|
1149
|
-
|
|
1150
|
-
retries = 0
|
|
1151
|
-
while True:
|
|
1152
|
-
response = requests.get(
|
|
1153
|
-
url=self.get_all_priorities_url(),
|
|
1154
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1155
|
-
cookies=self.cookie(),
|
|
1156
|
-
timeout=REQUEST_TIMEOUT,
|
|
1157
|
-
)
|
|
1158
|
-
if response.ok:
|
|
1159
|
-
authenticate_dict = self.parse_request_response(
|
|
1160
|
-
response_object=response,
|
|
1161
|
-
additional_error_message="This can be normal during restart",
|
|
1162
|
-
show_error=False,
|
|
1163
|
-
)
|
|
1164
|
-
if not authenticate_dict:
|
|
1165
|
-
return None
|
|
1166
|
-
return authenticate_dict
|
|
1167
|
-
if response.status_code == 401 and retries == 0:
|
|
1168
|
-
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
1169
|
-
self.authenticate(revalidate=True)
|
|
1170
|
-
retries += 1
|
|
1171
|
-
self.logger.error(response.text)
|
|
1172
|
-
return None
|
|
1173
|
-
|
|
1174
|
-
# end method definition
|
|
1175
|
-
|
|
1176
|
-
def create_customer(
|
|
1177
|
-
self,
|
|
1178
|
-
customer_name: str,
|
|
1179
|
-
legal_business_name: str,
|
|
1180
|
-
trading_name: str,
|
|
1181
|
-
) -> dict | None:
|
|
1182
|
-
"""Create customer entity instance.
|
|
1183
|
-
|
|
1184
|
-
Args:
|
|
1185
|
-
customer_name (str):
|
|
1186
|
-
The name of the customer.
|
|
1187
|
-
legal_business_name (str):
|
|
1188
|
-
The legal business name.
|
|
1189
|
-
trading_name (str):
|
|
1190
|
-
The trading name.
|
|
1191
|
-
|
|
1192
|
-
Returns:
|
|
1193
|
-
dict:
|
|
1194
|
-
Request response (dictionary) or None if the REST call fails.
|
|
1195
|
-
|
|
1196
|
-
"""
|
|
1197
|
-
|
|
1198
|
-
create_customer = {
|
|
1199
|
-
"Properties": {
|
|
1200
|
-
"CustomerName": customer_name,
|
|
1201
|
-
"LegalBusinessName": legal_business_name,
|
|
1202
|
-
"TradingName": trading_name,
|
|
1820
|
+
Example:
|
|
1821
|
+
{
|
|
1822
|
+
'Identity': {
|
|
1823
|
+
'Id': '327681'
|
|
1203
1824
|
},
|
|
1825
|
+
'_links': {
|
|
1826
|
+
'self': {
|
|
1827
|
+
'href': '/OpentextCaseManagement/entities/Priority/items/327681'
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1204
1830
|
}
|
|
1205
|
-
retries = 0
|
|
1206
|
-
while True:
|
|
1207
|
-
response = requests.post(
|
|
1208
|
-
url=self.create_customer_url(),
|
|
1209
|
-
json=create_customer,
|
|
1210
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1211
|
-
cookies=self.cookie(),
|
|
1212
|
-
timeout=REQUEST_TIMEOUT,
|
|
1213
|
-
)
|
|
1214
|
-
if response.ok:
|
|
1215
|
-
self.logger.info("Customer record created successfully")
|
|
1216
|
-
return self.parse_request_response(
|
|
1217
|
-
response_object=response,
|
|
1218
|
-
additional_error_message="This can be normal during restart",
|
|
1219
|
-
show_error=False,
|
|
1220
|
-
)
|
|
1221
|
-
if response.status_code == 401 and retries == 0:
|
|
1222
|
-
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
1223
|
-
self.authenticate(revalidate=True)
|
|
1224
|
-
retries += 1
|
|
1225
|
-
self.logger.error(response.text)
|
|
1226
|
-
return None
|
|
1227
|
-
|
|
1228
|
-
# end method definition
|
|
1229
|
-
|
|
1230
|
-
def get_all_customers(self) -> dict | None:
|
|
1231
|
-
"""Get all customer entity imstances.
|
|
1232
|
-
|
|
1233
|
-
Args:
|
|
1234
|
-
None
|
|
1235
|
-
|
|
1236
|
-
Returns:
|
|
1237
|
-
dict:
|
|
1238
|
-
Request response (dictionary) or None if the REST call fails
|
|
1239
1831
|
|
|
1240
1832
|
"""
|
|
1241
1833
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
url=self.get_all_customeres_url(),
|
|
1246
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1247
|
-
cookies=self.cookie(),
|
|
1248
|
-
timeout=REQUEST_TIMEOUT,
|
|
1249
|
-
)
|
|
1250
|
-
if response.ok:
|
|
1251
|
-
authenticate_dict = self.parse_request_response(
|
|
1252
|
-
response_object=response,
|
|
1253
|
-
additional_error_message="This can be normal during restart",
|
|
1254
|
-
show_error=False,
|
|
1255
|
-
)
|
|
1256
|
-
if not authenticate_dict:
|
|
1257
|
-
return None
|
|
1258
|
-
return authenticate_dict
|
|
1259
|
-
if response.status_code == 401 and retries == 0:
|
|
1260
|
-
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
1261
|
-
self.authenticate(revalidate=True)
|
|
1262
|
-
retries += 1
|
|
1263
|
-
self.logger.error(response.text)
|
|
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!")
|
|
1264
1837
|
return None
|
|
1265
1838
|
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
def create_case_type(self, name: str, description: str, status: int) -> dict | None:
|
|
1269
|
-
"""Create case type entity instances.
|
|
1270
|
-
|
|
1271
|
-
Args:
|
|
1272
|
-
name (str):
|
|
1273
|
-
The name of the case type.
|
|
1274
|
-
description (str):
|
|
1275
|
-
The description of the case type.
|
|
1276
|
-
status (str): status
|
|
1277
|
-
|
|
1278
|
-
Returns:
|
|
1279
|
-
dict:
|
|
1280
|
-
Request response (dictionary) or None if the REST call fails.
|
|
1281
|
-
|
|
1282
|
-
"""
|
|
1283
|
-
|
|
1284
|
-
create_case_type = {
|
|
1839
|
+
create_priority_data = {
|
|
1285
1840
|
"Properties": {"Name": name, "Description": description, "Status": status},
|
|
1286
1841
|
}
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
response_object=response,
|
|
1300
|
-
additional_error_message="This can be normal during restart",
|
|
1301
|
-
show_error=False,
|
|
1302
|
-
)
|
|
1303
|
-
if response.status_code == 401 and retries == 0:
|
|
1304
|
-
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
1305
|
-
self.authenticate(revalidate=True)
|
|
1306
|
-
retries += 1
|
|
1307
|
-
self.logger.error(response.text)
|
|
1308
|
-
return None
|
|
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
|
+
)
|
|
1309
1854
|
|
|
1310
1855
|
# end method definition
|
|
1311
1856
|
|
|
1312
|
-
def
|
|
1313
|
-
"""Get all
|
|
1857
|
+
def get_priorities(self) -> dict | None:
|
|
1858
|
+
"""Get all priorities from entity.
|
|
1314
1859
|
|
|
1315
1860
|
Args:
|
|
1316
1861
|
None
|
|
1317
1862
|
|
|
1318
1863
|
Returns:
|
|
1319
1864
|
dict:
|
|
1320
|
-
Request response (dictionary) or None if the REST call fails.
|
|
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
|
+
}
|
|
1321
1912
|
|
|
1322
1913
|
"""
|
|
1323
1914
|
|
|
1324
|
-
|
|
1325
|
-
while True:
|
|
1326
|
-
response = requests.get(
|
|
1327
|
-
url=self.get_all_case_types_url(),
|
|
1328
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1329
|
-
cookies=self.cookie(),
|
|
1330
|
-
timeout=REQUEST_TIMEOUT,
|
|
1331
|
-
)
|
|
1332
|
-
if response.ok:
|
|
1333
|
-
authenticate_dict = self.parse_request_response(
|
|
1334
|
-
response_object=response,
|
|
1335
|
-
additional_error_message="This can be normal during restart",
|
|
1336
|
-
show_error=False,
|
|
1337
|
-
)
|
|
1338
|
-
if not authenticate_dict:
|
|
1339
|
-
return None
|
|
1340
|
-
return authenticate_dict
|
|
1341
|
-
if response.status_code == 401 and retries == 0:
|
|
1342
|
-
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
1343
|
-
self.authenticate(revalidate=True)
|
|
1344
|
-
retries += 1
|
|
1345
|
-
self.logger.error(response.text)
|
|
1346
|
-
return None
|
|
1915
|
+
request_url = self.get_priorities_list_url()
|
|
1347
1916
|
|
|
1348
|
-
|
|
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
|
+
)
|
|
1349
1925
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
name: str,
|
|
1355
|
-
status: int,
|
|
1356
|
-
) -> dict | None:
|
|
1357
|
-
"""Create category entity instance.
|
|
1926
|
+
# end method definition
|
|
1927
|
+
|
|
1928
|
+
def get_priority_by_name(self, name: str) -> dict | None:
|
|
1929
|
+
"""Get priority entity instance by its name.
|
|
1358
1930
|
|
|
1359
1931
|
Args:
|
|
1360
|
-
case_prefix (str):
|
|
1361
|
-
The prefix for the case.
|
|
1362
|
-
description (str):
|
|
1363
|
-
The description for the category.
|
|
1364
1932
|
name (str):
|
|
1365
|
-
The name of the
|
|
1366
|
-
status (int):
|
|
1367
|
-
The status code.
|
|
1933
|
+
The name of the priority.
|
|
1368
1934
|
|
|
1369
1935
|
Returns:
|
|
1370
|
-
dict:
|
|
1371
|
-
|
|
1936
|
+
dict | None:
|
|
1937
|
+
Returns the priority item or None if it does not exist.
|
|
1372
1938
|
|
|
1373
1939
|
"""
|
|
1374
1940
|
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
"Description": description,
|
|
1379
|
-
"Name": name,
|
|
1380
|
-
"Status": status,
|
|
1381
|
-
},
|
|
1382
|
-
}
|
|
1383
|
-
retries = 0
|
|
1384
|
-
while True:
|
|
1385
|
-
response = requests.post(
|
|
1386
|
-
url=self.create_category_url(),
|
|
1387
|
-
json=create_categoty,
|
|
1388
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1389
|
-
cookies=self.cookie(),
|
|
1390
|
-
timeout=REQUEST_TIMEOUT,
|
|
1391
|
-
)
|
|
1392
|
-
if response.ok:
|
|
1393
|
-
self.logger.info("Category created successfully")
|
|
1394
|
-
return self.parse_request_response(
|
|
1395
|
-
response_object=response,
|
|
1396
|
-
additional_error_message="This can be normal during restart",
|
|
1397
|
-
show_error=False,
|
|
1398
|
-
)
|
|
1399
|
-
if response.status_code == 401 and retries == 0:
|
|
1400
|
-
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
1401
|
-
self.authenticate(revalidate=True)
|
|
1402
|
-
retries += 1
|
|
1403
|
-
self.logger.error(response.text)
|
|
1404
|
-
return None
|
|
1941
|
+
priorities = self.get_priorities()
|
|
1942
|
+
|
|
1943
|
+
return self.get_result_item(response=priorities, entity_type="PriorityList", key="Name", value=name)
|
|
1405
1944
|
|
|
1406
1945
|
# end method definition
|
|
1407
1946
|
|
|
1408
|
-
def
|
|
1409
|
-
"""Get all
|
|
1947
|
+
def get_priority_ids(self) -> list:
|
|
1948
|
+
"""Get all priority entity instances IDs.
|
|
1410
1949
|
|
|
1411
1950
|
Args:
|
|
1412
1951
|
None
|
|
1413
1952
|
Returns:
|
|
1414
|
-
|
|
1415
|
-
|
|
1953
|
+
list:
|
|
1954
|
+
A list with all priority IDs.
|
|
1416
1955
|
|
|
1417
1956
|
"""
|
|
1418
1957
|
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
url=self.get_all_categories_url(),
|
|
1423
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1424
|
-
cookies=self.cookie(),
|
|
1425
|
-
timeout=REQUEST_TIMEOUT,
|
|
1426
|
-
)
|
|
1427
|
-
if response.ok:
|
|
1428
|
-
authenticate_dict = self.parse_request_response(
|
|
1429
|
-
response_object=response,
|
|
1430
|
-
additional_error_message="This can be normal during restart",
|
|
1431
|
-
show_error=False,
|
|
1432
|
-
)
|
|
1433
|
-
if not authenticate_dict:
|
|
1434
|
-
return None
|
|
1435
|
-
return authenticate_dict
|
|
1436
|
-
if response.status_code == 401 and retries == 0:
|
|
1437
|
-
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
1438
|
-
self.authenticate(revalidate=True)
|
|
1439
|
-
retries += 1
|
|
1440
|
-
self.logger.error(response.text)
|
|
1441
|
-
return None
|
|
1958
|
+
priorities = self.get_priorities()
|
|
1959
|
+
|
|
1960
|
+
return self.get_result_values(response=priorities, entity_type="PriorityList", key="id") or []
|
|
1442
1961
|
|
|
1443
1962
|
# end method definition
|
|
1444
1963
|
|
|
1445
|
-
def
|
|
1964
|
+
def create_customer(
|
|
1446
1965
|
self,
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
parent_id: int,
|
|
1966
|
+
customer_name: str,
|
|
1967
|
+
legal_business_name: str,
|
|
1968
|
+
trading_name: str,
|
|
1451
1969
|
) -> dict | None:
|
|
1452
|
-
"""Create
|
|
1970
|
+
"""Create customer entity instance.
|
|
1453
1971
|
|
|
1454
1972
|
Args:
|
|
1455
|
-
|
|
1456
|
-
The name of the
|
|
1457
|
-
|
|
1458
|
-
The
|
|
1459
|
-
|
|
1460
|
-
The
|
|
1461
|
-
parent_id (int):
|
|
1462
|
-
The parent ID of the category.
|
|
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.
|
|
1463
1979
|
|
|
1464
1980
|
Returns:
|
|
1465
1981
|
dict:
|
|
@@ -1467,786 +1983,810 @@ class OTAWP:
|
|
|
1467
1983
|
|
|
1468
1984
|
"""
|
|
1469
1985
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
retries = 0
|
|
1474
|
-
while True:
|
|
1475
|
-
base_url = self.baseurl()
|
|
1476
|
-
endpoint = "/app/entityRestService/api/OpentextCaseManagement/entities/Category/items/"
|
|
1477
|
-
child_path = "/childEntities/SubCategory?defaultinst_ct=abcd"
|
|
1478
|
-
response = requests.post(
|
|
1479
|
-
url=base_url + endpoint + str(parent_id) + child_path,
|
|
1480
|
-
json=create_sub_categoty,
|
|
1481
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1482
|
-
cookies=self.cookie(),
|
|
1483
|
-
timeout=REQUEST_TIMEOUT,
|
|
1484
|
-
)
|
|
1485
|
-
if response.ok:
|
|
1486
|
-
self.logger.info("Sub category created successfully")
|
|
1487
|
-
return self.parse_request_response(
|
|
1488
|
-
response_object=response,
|
|
1489
|
-
additional_error_message="This can be normal during restart",
|
|
1490
|
-
show_error=False,
|
|
1491
|
-
)
|
|
1492
|
-
if response.status_code == 401 and retries == 0:
|
|
1493
|
-
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
1494
|
-
self.authenticate(revalidate=True)
|
|
1495
|
-
retries += 1
|
|
1496
|
-
self.logger.error(response.text)
|
|
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!")
|
|
1497
1989
|
return None
|
|
1498
1990
|
|
|
1991
|
+
create_customer_data = {
|
|
1992
|
+
"Properties": {
|
|
1993
|
+
"CustomerName": customer_name,
|
|
1994
|
+
"LegalBusinessName": legal_business_name,
|
|
1995
|
+
"TradingName": trading_name,
|
|
1996
|
+
},
|
|
1997
|
+
}
|
|
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
|
+
)
|
|
2010
|
+
|
|
1499
2011
|
# end method definition
|
|
1500
2012
|
|
|
1501
|
-
def
|
|
1502
|
-
"""Get all
|
|
2013
|
+
def get_customers(self) -> dict | None:
|
|
2014
|
+
"""Get all customer entity instances.
|
|
1503
2015
|
|
|
1504
2016
|
Args:
|
|
1505
|
-
|
|
1506
|
-
The parent ID of the sub categories.
|
|
2017
|
+
None
|
|
1507
2018
|
|
|
1508
2019
|
Returns:
|
|
1509
|
-
dict:
|
|
2020
|
+
dict | None:
|
|
1510
2021
|
Request response (dictionary) or None if the REST call fails.
|
|
1511
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
|
+
|
|
1512
2060
|
"""
|
|
1513
2061
|
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
)
|
|
1525
|
-
if response.ok:
|
|
1526
|
-
authenticate_dict = self.parse_request_response(
|
|
1527
|
-
response_object=response,
|
|
1528
|
-
additional_error_message="This can be normal during restart",
|
|
1529
|
-
show_error=False,
|
|
1530
|
-
)
|
|
1531
|
-
if not authenticate_dict:
|
|
1532
|
-
return None
|
|
1533
|
-
return authenticate_dict
|
|
1534
|
-
if response.status_code == 401 and retries == 0:
|
|
1535
|
-
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
1536
|
-
self.authenticate(revalidate=True)
|
|
1537
|
-
retries += 1
|
|
1538
|
-
self.logger.error(response.text)
|
|
1539
|
-
return None
|
|
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
|
+
)
|
|
1540
2072
|
|
|
1541
2073
|
# end method definition
|
|
1542
2074
|
|
|
1543
|
-
def
|
|
1544
|
-
|
|
1545
|
-
subject: str,
|
|
1546
|
-
description: str,
|
|
1547
|
-
loan_amount: str,
|
|
1548
|
-
loan_duration_in_months: str,
|
|
1549
|
-
category: str,
|
|
1550
|
-
subcategory: str,
|
|
1551
|
-
priority: str,
|
|
1552
|
-
service: str,
|
|
1553
|
-
customer: str,
|
|
1554
|
-
) -> dict | None:
|
|
1555
|
-
"""Create loan entity instance.
|
|
2075
|
+
def get_customer_by_name(self, name: str) -> dict | None:
|
|
2076
|
+
"""Get customer entity instance by its name.
|
|
1556
2077
|
|
|
1557
2078
|
Args:
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
loan_amount (str): loan_amount
|
|
1561
|
-
loan_duration_in_months (str): loan_duration_in_months
|
|
1562
|
-
category (str): category
|
|
1563
|
-
subcategory (str): subcategory
|
|
1564
|
-
priority (str): priority
|
|
1565
|
-
service (str): service
|
|
1566
|
-
customer (str): customer
|
|
1567
|
-
Returns:
|
|
1568
|
-
dict: Request response (dictionary) or None if the REST call fails
|
|
1569
|
-
|
|
1570
|
-
"""
|
|
1571
|
-
|
|
1572
|
-
create_loan = f"""<SOAP:Envelope xmlns:SOAP=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n
|
|
1573
|
-
<SOAP:Body>\r\n
|
|
1574
|
-
<CreateCase xmlns=\"http://schemas/OpentextCaseManagement/Case/operations\">\r\n
|
|
1575
|
-
<ns0:Case-create xmlns:ns0=\"http://schemas/OpentextCaseManagement/Case\">\r\n
|
|
1576
|
-
<ns0:Subject>{subject}</ns0:Subject>\r\n
|
|
1577
|
-
<ns0:Description>{description}</ns0:Description>\r\n
|
|
1578
|
-
<ns0:LoanAmount>{loan_amount}</ns0:LoanAmount>\r\n
|
|
1579
|
-
<ns0:LoanDurationInMonths>{loan_duration_in_months}</ns0:LoanDurationInMonths>\r\n
|
|
1580
|
-
\r\n
|
|
1581
|
-
<ns0:CaseType>\r\n
|
|
1582
|
-
<ns1:CaseType-id xmlns:ns1=\"http://schemas/OpentextCaseManagement/CaseType\">\r\n
|
|
1583
|
-
<ns1:Id>{service}</ns1:Id>\r\n
|
|
1584
|
-
</ns1:CaseType-id>\r\n
|
|
1585
|
-
</ns0:CaseType>\r\n
|
|
1586
|
-
\r\n
|
|
1587
|
-
<ns0:Category>\r\n
|
|
1588
|
-
<ns2:Category-id xmlns:ns2=\"http://schemas/OpentextCaseManagement/Category\">\r\n
|
|
1589
|
-
<ns2:Id>{category}</ns2:Id>\r\n
|
|
1590
|
-
</ns2:Category-id>\r\n
|
|
1591
|
-
</ns0:Category>\r\n
|
|
1592
|
-
\r\n
|
|
1593
|
-
<ns0:SubCategory>\r\n
|
|
1594
|
-
<ns5:SubCategory-id xmlns:ns5=\"http://schemas/OpentextCaseManagement/Category.SubCategory\">\r\n
|
|
1595
|
-
<ns5:Id>{category}</ns5:Id>\r\n
|
|
1596
|
-
<ns5:Id1>{subcategory}</ns5:Id1>\r\n
|
|
1597
|
-
</ns5:SubCategory-id>\r\n
|
|
1598
|
-
</ns0:SubCategory>\r\n
|
|
1599
|
-
\r\n
|
|
1600
|
-
<ns0:Priority>\r\n
|
|
1601
|
-
<ns3:Priority-id xmlns:ns3=\"http://schemas/OpentextCaseManagement/Priority\">\r\n
|
|
1602
|
-
<ns3:Id>{priority}</ns3:Id>\r\n
|
|
1603
|
-
</ns3:Priority-id>\r\n
|
|
1604
|
-
</ns0:Priority>\r\n
|
|
1605
|
-
\r\n
|
|
1606
|
-
<ns0:ToCustomer>\r\n
|
|
1607
|
-
<ns9:Customer-id xmlns:ns9=\"http://schemas/OpentextCaseManagement/Customer\">\r\n
|
|
1608
|
-
<ns9:Id>{customer}</ns9:Id>\r\n
|
|
1609
|
-
</ns9:Customer-id>\r\n
|
|
1610
|
-
</ns0:ToCustomer>\r\n
|
|
1611
|
-
\r\n
|
|
1612
|
-
</ns0:Case-create>\r\n
|
|
1613
|
-
</CreateCase>\r\n
|
|
1614
|
-
</SOAP:Body>\r\n
|
|
1615
|
-
</SOAP:Envelope>"""
|
|
2079
|
+
name (str):
|
|
2080
|
+
The name of the customer.
|
|
1616
2081
|
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
if response.ok:
|
|
1627
|
-
self.logger.info("Loan created successfully")
|
|
1628
|
-
return self.parse_xml(response.text)
|
|
1629
|
-
if response.status_code == 401 and retries == 0:
|
|
1630
|
-
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
1631
|
-
self.authenticate(revalidate=True)
|
|
1632
|
-
retries += 1
|
|
1633
|
-
self.logger.error(response.text)
|
|
1634
|
-
return None
|
|
2082
|
+
Returns:
|
|
2083
|
+
dict | None:
|
|
2084
|
+
Returns the customer data or None if no customer with the given name exists.
|
|
2085
|
+
|
|
2086
|
+
"""
|
|
2087
|
+
|
|
2088
|
+
customers = self.get_customers()
|
|
2089
|
+
|
|
2090
|
+
return self.get_result_item(response=customers, entity_type="CustomerList", key="CustomerName", value=name)
|
|
1635
2091
|
|
|
1636
2092
|
# end method definition
|
|
1637
2093
|
|
|
1638
|
-
def
|
|
1639
|
-
"""Get all
|
|
2094
|
+
def get_customer_ids(self) -> list:
|
|
2095
|
+
"""Get all customer entity instances IDs.
|
|
1640
2096
|
|
|
1641
2097
|
Args:
|
|
1642
|
-
|
|
1643
|
-
|
|
2098
|
+
None
|
|
1644
2099
|
Returns:
|
|
1645
|
-
|
|
1646
|
-
|
|
2100
|
+
list:
|
|
2101
|
+
A list of all customer IDs.
|
|
1647
2102
|
|
|
1648
2103
|
"""
|
|
1649
2104
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
url=self.get_all_loans_url(),
|
|
1654
|
-
headers=REQUEST_HEADERS_JSON,
|
|
1655
|
-
cookies=self.cookie(),
|
|
1656
|
-
timeout=REQUEST_TIMEOUT,
|
|
1657
|
-
)
|
|
1658
|
-
if response.ok:
|
|
1659
|
-
authenticate_dict = self.parse_request_response(
|
|
1660
|
-
response_object=response,
|
|
1661
|
-
additional_error_message="This can be normal during restart",
|
|
1662
|
-
show_error=False,
|
|
1663
|
-
)
|
|
1664
|
-
if not authenticate_dict:
|
|
1665
|
-
return None
|
|
1666
|
-
return authenticate_dict
|
|
1667
|
-
if response.status_code == 401 and retries == 0:
|
|
1668
|
-
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
1669
|
-
self.authenticate(revalidate=True)
|
|
1670
|
-
retries += 1
|
|
1671
|
-
else:
|
|
1672
|
-
self.logger.error(response.text)
|
|
1673
|
-
return None
|
|
2105
|
+
customers = self.get_customers()
|
|
2106
|
+
|
|
2107
|
+
return self.get_result_values(response=customers, entity_type="CustomerList", key="id") or []
|
|
1674
2108
|
|
|
1675
2109
|
# end method definition
|
|
1676
2110
|
|
|
1677
|
-
def
|
|
1678
|
-
"""
|
|
2111
|
+
def create_case_type(self, name: str, description: str = "", status: int = 1) -> dict | None:
|
|
2112
|
+
"""Create case type entity instances.
|
|
1679
2113
|
|
|
1680
2114
|
Args:
|
|
1681
|
-
|
|
1682
|
-
|
|
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
|
|
1683
2120
|
|
|
1684
2121
|
Returns:
|
|
1685
|
-
|
|
2122
|
+
dict:
|
|
2123
|
+
Request response (dictionary) or None if the REST call fails.
|
|
1686
2124
|
|
|
1687
2125
|
"""
|
|
1688
2126
|
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
)
|
|
1694
|
-
return True
|
|
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!")
|
|
2130
|
+
return None
|
|
1695
2131
|
|
|
1696
|
-
|
|
1697
|
-
"
|
|
1698
|
-
|
|
2132
|
+
create_case_type_data = {
|
|
2133
|
+
"Properties": {"Name": name, "Description": description, "Status": status},
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
request_url = self.get_create_casetype_url()
|
|
2137
|
+
|
|
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),
|
|
1699
2146
|
)
|
|
1700
|
-
return False
|
|
1701
2147
|
|
|
1702
2148
|
# end method definition
|
|
1703
2149
|
|
|
1704
|
-
def
|
|
1705
|
-
"""
|
|
2150
|
+
def get_case_types(self) -> dict | None:
|
|
2151
|
+
"""Get all case type entity instances.
|
|
1706
2152
|
|
|
1707
2153
|
Args:
|
|
1708
|
-
|
|
1709
|
-
The response.
|
|
1710
|
-
workspace_name (str):
|
|
1711
|
-
The name of the workspace.
|
|
2154
|
+
None
|
|
1712
2155
|
|
|
1713
2156
|
Returns:
|
|
1714
|
-
|
|
1715
|
-
|
|
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
|
+
}
|
|
1716
2196
|
|
|
1717
2197
|
"""
|
|
1718
2198
|
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
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",
|
|
1728
2208
|
)
|
|
1729
|
-
return False
|
|
1730
2209
|
|
|
1731
2210
|
# end method definition
|
|
1732
2211
|
|
|
1733
|
-
def
|
|
1734
|
-
|
|
1735
|
-
workspace_name: str,
|
|
1736
|
-
workspace_gui_id: str,
|
|
1737
|
-
) -> dict | None:
|
|
1738
|
-
"""Call create_workspace and retries if the response contains specific error messages.
|
|
1739
|
-
|
|
1740
|
-
Retries until the response does not contain the errors or a max retry limit is reached.
|
|
2212
|
+
def get_case_type_by_name(self, name: str) -> dict | None:
|
|
2213
|
+
"""Get case type entity instance by its name.
|
|
1741
2214
|
|
|
1742
2215
|
Args:
|
|
1743
|
-
|
|
1744
|
-
The
|
|
1745
|
-
workspace_gui_id (str):
|
|
1746
|
-
The workspace GUI ID.
|
|
2216
|
+
name (str):
|
|
2217
|
+
The name of the case type.
|
|
1747
2218
|
|
|
1748
2219
|
Returns:
|
|
1749
2220
|
dict | None:
|
|
1750
|
-
|
|
2221
|
+
Returns the case type data or None if no case type with the given name exists.
|
|
1751
2222
|
|
|
1752
2223
|
"""
|
|
1753
2224
|
|
|
1754
|
-
|
|
1755
|
-
retries = 0
|
|
1756
|
-
error_messages = [
|
|
1757
|
-
"Collaborative Workspace Service Container is not able to handle the SOAP request",
|
|
1758
|
-
"Service Group Lookup failure",
|
|
1759
|
-
]
|
|
1760
|
-
|
|
1761
|
-
while retries < max_retries:
|
|
1762
|
-
response = self.create_workspace(workspace_name, workspace_gui_id)
|
|
1763
|
-
|
|
1764
|
-
# Check if any error message is in the response
|
|
1765
|
-
if any(error_message in response for error_message in error_messages):
|
|
1766
|
-
self.logger.info(
|
|
1767
|
-
"Workspace service error, waiting 60 seconds to retry... (Retry %d of %d)",
|
|
1768
|
-
retries + 1,
|
|
1769
|
-
max_retries,
|
|
1770
|
-
)
|
|
1771
|
-
time.sleep(60)
|
|
1772
|
-
retries += 1
|
|
1773
|
-
else:
|
|
1774
|
-
self.logger.info("Collaborative Workspace Service Container is ready")
|
|
1775
|
-
return response
|
|
2225
|
+
case_types = self.get_case_types()
|
|
1776
2226
|
|
|
1777
|
-
|
|
1778
|
-
self.logger.error(
|
|
1779
|
-
"Max retries reached for workspace -> '%s', unable to create successfully.",
|
|
1780
|
-
workspace_name,
|
|
1781
|
-
)
|
|
1782
|
-
return response
|
|
2227
|
+
return self.get_result_item(response=case_types, entity_type="AllCaseTypes", key="Name", value=name)
|
|
1783
2228
|
|
|
1784
2229
|
# end method definition
|
|
1785
2230
|
|
|
1786
|
-
def
|
|
1787
|
-
"""
|
|
2231
|
+
def get_case_type_ids(self) -> list:
|
|
2232
|
+
"""Get All CaseType entity instances IDs.
|
|
1788
2233
|
|
|
1789
2234
|
Args:
|
|
1790
2235
|
None
|
|
2236
|
+
|
|
1791
2237
|
Returns:
|
|
1792
|
-
|
|
2238
|
+
list:
|
|
2239
|
+
List of all case type IDs.
|
|
1793
2240
|
|
|
1794
2241
|
"""
|
|
1795
2242
|
|
|
1796
|
-
self.
|
|
1797
|
-
category_resp_dict = []
|
|
1798
|
-
if not self.verify_category_exists("Short Term Loan"):
|
|
1799
|
-
self.create_category("LOAN", "Short Term Loan", "Short Term Loan", 1)
|
|
1800
|
-
if not self.verify_category_exists("Long Term Loan"):
|
|
1801
|
-
self.create_category("LOAN", "Long Term Loan", "Long Term Loan", 1)
|
|
1802
|
-
if not self.verify_category_exists("Flexi Loan"):
|
|
1803
|
-
self.create_category("LOAN", "Flexi Loan", "Flexi Loan", 1)
|
|
1804
|
-
category_resp_dict = self.get_category_lists()
|
|
1805
|
-
self.logger.debug(" RUNTIME -->> Category instance creation ended")
|
|
2243
|
+
case_types = self.get_case_types()
|
|
1806
2244
|
|
|
1807
|
-
|
|
1808
|
-
self.logger.debug(
|
|
1809
|
-
" RUNTIME -->> Sub Category instance creation started ........",
|
|
1810
|
-
)
|
|
1811
|
-
stl = 0
|
|
1812
|
-
ltl = 0
|
|
1813
|
-
fl = 0
|
|
1814
|
-
if not self.verify_sub_category_exists("Business", 0, category_resp_dict):
|
|
1815
|
-
response_dict = self.create_sub_categoy(
|
|
1816
|
-
"Business",
|
|
1817
|
-
"Business",
|
|
1818
|
-
1,
|
|
1819
|
-
category_resp_dict[0],
|
|
1820
|
-
)
|
|
1821
|
-
stl = response_dict["Identity"]["Id"]
|
|
1822
|
-
self.logger.info("Sub category id stl: %s ", stl)
|
|
1823
|
-
else:
|
|
1824
|
-
stl = self.return_sub_category_exists_id("Business", 0, category_resp_dict)
|
|
1825
|
-
self.logger.info("Sub category id stl -> %s ", stl)
|
|
1826
|
-
|
|
1827
|
-
if not self.verify_sub_category_exists("Business", 1, category_resp_dict):
|
|
1828
|
-
response_dict = self.create_sub_categoy(
|
|
1829
|
-
"Business",
|
|
1830
|
-
"Business",
|
|
1831
|
-
1,
|
|
1832
|
-
category_resp_dict[1],
|
|
1833
|
-
)
|
|
1834
|
-
ltl = response_dict["Identity"]["Id"]
|
|
1835
|
-
self.logger.info("Sub category id ltl -> %s ", ltl)
|
|
1836
|
-
else:
|
|
1837
|
-
ltl = self.return_sub_category_exists_id(name="Business", index=1, category_resp_dict=category_resp_dict)
|
|
1838
|
-
self.logger.info("Sub category id ltl -> %s ", ltl)
|
|
1839
|
-
if not self.verify_sub_category_exists(name="Business", index=2, category_resp_dict=category_resp_dict):
|
|
1840
|
-
response_dict = self.create_sub_categoy(
|
|
1841
|
-
"Business",
|
|
1842
|
-
"Business",
|
|
1843
|
-
1,
|
|
1844
|
-
category_resp_dict[2],
|
|
1845
|
-
)
|
|
1846
|
-
fl = response_dict["Identity"]["Id"]
|
|
1847
|
-
self.logger.info("Sub category id fl -> %s ", fl)
|
|
1848
|
-
else:
|
|
1849
|
-
fl = self.return_sub_category_exists_id(name="Business", index=2, category_resp_dict=category_resp_dict)
|
|
1850
|
-
self.logger.info("Sub category id fl -> %s ", fl)
|
|
1851
|
-
self.logger.debug(" RUNTIME -->> Sub Category instance creation ended")
|
|
1852
|
-
|
|
1853
|
-
############################# Case Types
|
|
1854
|
-
self.logger.debug(" RUNTIME -->> Case Types instance creation started ........")
|
|
1855
|
-
case_type_list = []
|
|
1856
|
-
|
|
1857
|
-
if not self.vverify_case_type_exists("Query"):
|
|
1858
|
-
self.create_case_type("Query", "Query", 1)
|
|
1859
|
-
if not self.vverify_case_type_exists("Help"):
|
|
1860
|
-
self.create_case_type("Help", "Help", 1)
|
|
1861
|
-
if not self.vverify_case_type_exists("Update Contact Details"):
|
|
1862
|
-
self.create_case_type("Update Contact Details", "Update Contact Details", 1)
|
|
1863
|
-
if not self.vverify_case_type_exists("New Loan Request"):
|
|
1864
|
-
self.create_case_type("New Loan Request", "New Loan Request", 1)
|
|
1865
|
-
if not self.vverify_case_type_exists("Loan Closure"):
|
|
1866
|
-
self.create_case_type("Loan Closure", "Loan Closure", 1)
|
|
1867
|
-
case_type_list = self.get_case_type_lists()
|
|
1868
|
-
self.logger.debug(" RUNTIME -->> Case Types instance creation ended")
|
|
1869
|
-
|
|
1870
|
-
############################# CUSTMOR
|
|
1871
|
-
self.logger.debug(" RUNTIME -->> Customer instance creation stated ........")
|
|
1872
|
-
customer_list = []
|
|
1873
|
-
if not self.verify_customer_exists("InaPlex Limited"):
|
|
1874
|
-
self.create_customer(
|
|
1875
|
-
"InaPlex Limited",
|
|
1876
|
-
"InaPlex Limited",
|
|
1877
|
-
"InaPlex Limited",
|
|
1878
|
-
)
|
|
2245
|
+
return self.get_result_values(response=case_types, entity_type="AllCaseTypes", key="id") or []
|
|
1879
2246
|
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
2247
|
+
# end method definition
|
|
2248
|
+
|
|
2249
|
+
def create_category(
|
|
2250
|
+
self,
|
|
2251
|
+
case_prefix: str,
|
|
2252
|
+
name: str,
|
|
2253
|
+
description: str,
|
|
2254
|
+
status: int = 1,
|
|
2255
|
+
) -> dict | None:
|
|
2256
|
+
"""Create category entity instance.
|
|
2257
|
+
|
|
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.
|
|
2267
|
+
|
|
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
|
+
}
|
|
1886
2283
|
|
|
1887
|
-
|
|
1888
|
-
self.create_customer(
|
|
1889
|
-
"Jones Lang LaSalle",
|
|
1890
|
-
"Jones Lang LaSalle",
|
|
1891
|
-
"Jones Lang LaSalle",
|
|
1892
|
-
)
|
|
2284
|
+
"""
|
|
1893
2285
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
"Key Point Consulting",
|
|
1899
|
-
)
|
|
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
|
|
1900
2290
|
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
if not self.verify_priority_exists("Medium"):
|
|
1910
|
-
self.create_priority("Medium", "Medium", 1)
|
|
1911
|
-
if not self.verify_priority_exists("Low"):
|
|
1912
|
-
self.create_priority("Low", "Low", 1)
|
|
1913
|
-
prioity_list = self.get_priority_lists()
|
|
1914
|
-
self.logger.debug(" RUNTIME -->> priority instance creation ended")
|
|
1915
|
-
|
|
1916
|
-
############################# LOAN
|
|
1917
|
-
loan_for_business = "Loan for Business1"
|
|
1918
|
-
loan_for_corporate_business = "Loan for Corporate Business1"
|
|
1919
|
-
loan_for_business_loan_request = "Loan for Business Loan Request1"
|
|
1920
|
-
|
|
1921
|
-
self.logger.debug(" RUNTIME -->> loan instance creation started ........")
|
|
1922
|
-
loan_resp_dict = self.get_all_loan()
|
|
1923
|
-
names = [item["Properties"]["Subject"] for item in loan_resp_dict["_embedded"]["AllCasesList"]]
|
|
1924
|
-
if loan_for_business in names:
|
|
1925
|
-
self.logger.info("Customer record Loan_for_business exists")
|
|
1926
|
-
else:
|
|
1927
|
-
self.logger.info("Creating customer Record with Loan_for_business ")
|
|
1928
|
-
response_dict = self.create_loan(
|
|
1929
|
-
subject=loan_for_business,
|
|
1930
|
-
description=loan_for_business,
|
|
1931
|
-
loan_amount=1,
|
|
1932
|
-
loan_duration_in_months=2,
|
|
1933
|
-
category=category_resp_dict[0],
|
|
1934
|
-
subcategory=stl,
|
|
1935
|
-
priority=prioity_list[0],
|
|
1936
|
-
service=case_type_list[0],
|
|
1937
|
-
customer=customer_list[0],
|
|
1938
|
-
)
|
|
2291
|
+
create_category_data = {
|
|
2292
|
+
"Properties": {
|
|
2293
|
+
"CasePrefix": case_prefix,
|
|
2294
|
+
"Description": description,
|
|
2295
|
+
"Name": name,
|
|
2296
|
+
"Status": status,
|
|
2297
|
+
},
|
|
2298
|
+
}
|
|
1939
2299
|
|
|
1940
|
-
|
|
1941
|
-
self.logger.info("Customer record Loan_for_Corporate_Business exists")
|
|
1942
|
-
else:
|
|
1943
|
-
self.logger.info(
|
|
1944
|
-
"Creating customer Record with Loan_for_Corporate_Business ",
|
|
1945
|
-
)
|
|
1946
|
-
response_dict = self.create_loan(
|
|
1947
|
-
subject=loan_for_corporate_business,
|
|
1948
|
-
description=loan_for_corporate_business,
|
|
1949
|
-
loan_amount=1,
|
|
1950
|
-
loan_duration_in_months=2,
|
|
1951
|
-
category=category_resp_dict[1],
|
|
1952
|
-
subcategory=ltl,
|
|
1953
|
-
priority=prioity_list[1],
|
|
1954
|
-
service=case_type_list[1],
|
|
1955
|
-
customer=customer_list[1],
|
|
1956
|
-
)
|
|
2300
|
+
request_url = self.get_create_category_url()
|
|
1957
2301
|
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
loan_amount=1,
|
|
1968
|
-
loan_duration_in_months=2,
|
|
1969
|
-
category=category_resp_dict[2],
|
|
1970
|
-
subcategory=fl,
|
|
1971
|
-
priority=prioity_list[2],
|
|
1972
|
-
service=case_type_list[2],
|
|
1973
|
-
customer=customer_list[2],
|
|
1974
|
-
)
|
|
1975
|
-
self.logger.debug(" RUNTIME -->> loan instance creation ended")
|
|
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),
|
|
2310
|
+
)
|
|
1976
2311
|
|
|
1977
2312
|
# end method definition
|
|
1978
2313
|
|
|
1979
|
-
def
|
|
1980
|
-
"""Get
|
|
2314
|
+
def get_categories(self) -> dict | None:
|
|
2315
|
+
"""Get all categories entity instances.
|
|
1981
2316
|
|
|
1982
2317
|
Args:
|
|
1983
2318
|
None
|
|
1984
2319
|
Returns:
|
|
1985
|
-
|
|
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
|
+
}
|
|
1986
2363
|
|
|
1987
2364
|
"""
|
|
1988
2365
|
|
|
1989
|
-
|
|
1990
|
-
categoy_resp_dict = self.get_all_categories()
|
|
1991
|
-
for item in categoy_resp_dict["_embedded"]["CategoryList"]:
|
|
1992
|
-
first_item_href = item["_links"]["item"]["href"]
|
|
1993
|
-
integer_value = int(re.search(r"\d+", first_item_href).group())
|
|
1994
|
-
self.logger.info("Category created with ID -> %d", integer_value)
|
|
1995
|
-
category_resp_dict.append(integer_value)
|
|
1996
|
-
self.logger.info("All extracted category IDs -> %s", category_resp_dict)
|
|
2366
|
+
request_url = self.get_categories_list_url()
|
|
1997
2367
|
|
|
1998
|
-
return
|
|
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
|
+
)
|
|
1999
2376
|
|
|
2000
2377
|
# end method definition
|
|
2001
2378
|
|
|
2002
|
-
def
|
|
2003
|
-
"""Get
|
|
2379
|
+
def get_category_by_name(self, name: str) -> dict | None:
|
|
2380
|
+
"""Get category entity instance by its name.
|
|
2381
|
+
|
|
2382
|
+
The category ID is only provided by the 'href' in '_links' / 'item'.
|
|
2004
2383
|
|
|
2005
2384
|
Args:
|
|
2006
|
-
|
|
2385
|
+
name (str):
|
|
2386
|
+
The name of the category.
|
|
2007
2387
|
|
|
2008
2388
|
Returns:
|
|
2009
|
-
|
|
2010
|
-
|
|
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
|
+
}
|
|
2011
2406
|
|
|
2012
2407
|
"""
|
|
2013
2408
|
|
|
2014
|
-
|
|
2015
|
-
casetype_resp_dict = self.get_all_case_type()
|
|
2016
|
-
for item in casetype_resp_dict["_embedded"]["AllCaseTypes"]:
|
|
2017
|
-
first_item_href = item["_links"]["item"]["href"]
|
|
2018
|
-
integer_value = int(re.search(r"\d+", first_item_href).group())
|
|
2019
|
-
self.logger.info("Case type created with ID -> %d", integer_value)
|
|
2020
|
-
case_type_list.append(integer_value)
|
|
2021
|
-
self.logger.info("All extracted case type IDs -> %s", case_type_list)
|
|
2409
|
+
categories = self.get_categories()
|
|
2022
2410
|
|
|
2023
|
-
return
|
|
2411
|
+
return self.get_result_item(response=categories, entity_type="CategoryList", key="Name", value=name)
|
|
2024
2412
|
|
|
2025
2413
|
# end method definition
|
|
2026
2414
|
|
|
2027
|
-
def
|
|
2028
|
-
"""Get
|
|
2415
|
+
def get_category_ids(self) -> list:
|
|
2416
|
+
"""Get All category entity instances IDs.
|
|
2029
2417
|
|
|
2030
2418
|
Args:
|
|
2031
2419
|
None
|
|
2032
2420
|
Returns:
|
|
2033
|
-
list:
|
|
2034
|
-
A list of all customer IDs.
|
|
2421
|
+
list: list of category IDs
|
|
2035
2422
|
|
|
2036
2423
|
"""
|
|
2037
2424
|
|
|
2038
|
-
|
|
2039
|
-
customer_resp_dict = self.get_all_customers()
|
|
2040
|
-
for item in customer_resp_dict["_embedded"]["CustomerList"]:
|
|
2041
|
-
first_item_href = item["_links"]["item"]["href"]
|
|
2042
|
-
integer_value = int(re.search(r"\d+", first_item_href).group())
|
|
2043
|
-
self.logger.info("Customer created with ID -> %d", integer_value)
|
|
2044
|
-
customer_list.append(integer_value)
|
|
2045
|
-
self.logger.info("All extracted Customer IDs -> %s ", customer_list)
|
|
2425
|
+
categories = self.get_categories()
|
|
2046
2426
|
|
|
2047
|
-
return
|
|
2427
|
+
return self.get_result_values(response=categories, entity_type="CategoryList", key="id") or []
|
|
2048
2428
|
|
|
2049
2429
|
# end method definition
|
|
2050
2430
|
|
|
2051
|
-
def
|
|
2052
|
-
|
|
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.
|
|
2053
2439
|
|
|
2054
2440
|
Args:
|
|
2055
|
-
|
|
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
|
+
|
|
2056
2450
|
Returns:
|
|
2057
|
-
|
|
2058
|
-
|
|
2451
|
+
dict:
|
|
2452
|
+
Request response (dictionary) or None if the REST call fails.
|
|
2059
2453
|
|
|
2060
2454
|
"""
|
|
2061
2455
|
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
self.logger.
|
|
2068
|
-
|
|
2069
|
-
|
|
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
|
+
)
|
|
2070
2471
|
|
|
2071
|
-
return
|
|
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
|
+
)
|
|
2072
2483
|
|
|
2073
2484
|
# end method definition
|
|
2074
2485
|
|
|
2075
|
-
def
|
|
2076
|
-
"""
|
|
2486
|
+
def get_sub_categories(self, parent_id: int) -> dict | None:
|
|
2487
|
+
"""Get all sub categeries entity instances.
|
|
2077
2488
|
|
|
2078
2489
|
Args:
|
|
2079
|
-
|
|
2080
|
-
The
|
|
2490
|
+
parent_id (int):
|
|
2491
|
+
The parent ID of the sub categories.
|
|
2081
2492
|
|
|
2082
2493
|
Returns:
|
|
2083
|
-
|
|
2084
|
-
|
|
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
|
+
}
|
|
2085
2529
|
|
|
2086
2530
|
"""
|
|
2087
2531
|
|
|
2088
|
-
|
|
2089
|
-
names = [item["Properties"]["Name"] for item in categoy_resp_dict["_embedded"]["CategoryList"]]
|
|
2090
|
-
if name in names:
|
|
2091
|
-
self.logger.info("Category record -> '%s' already exists", name)
|
|
2092
|
-
return True
|
|
2093
|
-
self.logger.info("Creating category record -> '%s'", name)
|
|
2532
|
+
request_url = self.config()["categoryUrl"] + "/items/" + str(parent_id) + "/childEntities/SubCategory"
|
|
2094
2533
|
|
|
2095
|
-
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
|
+
)
|
|
2096
2542
|
|
|
2097
2543
|
# end method definition
|
|
2098
2544
|
|
|
2099
|
-
def
|
|
2100
|
-
"""
|
|
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.
|
|
2101
2547
|
|
|
2102
2548
|
Args:
|
|
2549
|
+
parent_id (int):
|
|
2550
|
+
The ID of the parent category.
|
|
2103
2551
|
name (str):
|
|
2104
|
-
The name of the
|
|
2552
|
+
The name of the sub category.
|
|
2105
2553
|
|
|
2106
2554
|
Returns:
|
|
2107
|
-
|
|
2108
|
-
Returns
|
|
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.
|
|
2109
2558
|
|
|
2110
2559
|
"""
|
|
2111
2560
|
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
if name in names:
|
|
2115
|
-
self.logger.info("Case type record -> '%s' already exists", name)
|
|
2116
|
-
return True
|
|
2117
|
-
self.logger.info("Creating case type 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)
|
|
2118
2563
|
|
|
2119
|
-
return
|
|
2564
|
+
return self.get_result_item(response=sub_categories, entity_type="SubCategory", key="Name", value=name)
|
|
2120
2565
|
|
|
2121
2566
|
# end method definition
|
|
2122
2567
|
|
|
2123
|
-
def
|
|
2124
|
-
"""
|
|
2568
|
+
def get_sub_category_id(self, parent_id: int, name: str) -> int | None:
|
|
2569
|
+
"""Get the sub category entity instance ID.
|
|
2125
2570
|
|
|
2126
2571
|
Args:
|
|
2572
|
+
parent_id (int):
|
|
2573
|
+
ID of the parent category.
|
|
2127
2574
|
name (str):
|
|
2128
|
-
The name of the
|
|
2575
|
+
The name of the sub-category.
|
|
2129
2576
|
|
|
2130
2577
|
Returns:
|
|
2131
|
-
|
|
2132
|
-
Returns
|
|
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.
|
|
2133
2581
|
|
|
2134
2582
|
"""
|
|
2135
2583
|
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
self.logger.info("Creating customer -> '%s'", name)
|
|
2142
|
-
return False
|
|
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
|
|
2587
|
+
|
|
2588
|
+
return sub_cat["Identity"].get("Id")
|
|
2143
2589
|
|
|
2144
2590
|
# end method definition
|
|
2145
2591
|
|
|
2146
|
-
def
|
|
2147
|
-
|
|
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.
|
|
2148
2611
|
|
|
2149
2612
|
Args:
|
|
2150
|
-
|
|
2151
|
-
The
|
|
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.
|
|
2152
2631
|
|
|
2153
2632
|
Returns:
|
|
2154
|
-
|
|
2155
|
-
|
|
2633
|
+
dict | None:
|
|
2634
|
+
Request response (dictionary) or None if the REST call fails
|
|
2156
2635
|
|
|
2157
2636
|
"""
|
|
2158
2637
|
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
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
|
+
}
|
|
2165
2647
|
|
|
2166
|
-
|
|
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>
|
|
2692
|
+
"""
|
|
2693
|
+
|
|
2694
|
+
request_url = self.gateway_url()
|
|
2695
|
+
|
|
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
|
|
2167
2736
|
|
|
2168
2737
|
# end method definition
|
|
2169
2738
|
|
|
2170
|
-
def
|
|
2171
|
-
|
|
2172
|
-
name: str,
|
|
2173
|
-
index: int,
|
|
2174
|
-
category_resp_dict: list,
|
|
2175
|
-
) -> bool:
|
|
2176
|
-
"""Verify sub category entity instance already exists.
|
|
2739
|
+
def get_cases(self) -> dict | None:
|
|
2740
|
+
"""Get all case entity instances.
|
|
2177
2741
|
|
|
2178
2742
|
Args:
|
|
2179
|
-
|
|
2180
|
-
The name of the sub category.
|
|
2181
|
-
index (int):
|
|
2182
|
-
The index of the sub category.
|
|
2183
|
-
category_resp_dict (list):
|
|
2184
|
-
TODO: add description
|
|
2743
|
+
None
|
|
2185
2744
|
|
|
2186
2745
|
Returns:
|
|
2187
|
-
|
|
2188
|
-
|
|
2746
|
+
dict:
|
|
2747
|
+
Request response (dictionary) or None if the REST call fails.
|
|
2189
2748
|
|
|
2190
2749
|
"""
|
|
2191
2750
|
|
|
2192
|
-
|
|
2193
|
-
names = [item["Properties"]["Name"] for item in subcategoy_resp_dict["_embedded"]["SubCategory"]]
|
|
2194
|
-
stl = 0
|
|
2195
|
-
if name in names:
|
|
2196
|
-
self.logger.info("Sub category -> '%s' already exists", name)
|
|
2197
|
-
for item in subcategoy_resp_dict["_embedded"]["SubCategory"]:
|
|
2198
|
-
stl = item["Identity"]["Id"]
|
|
2199
|
-
self.logger.info("Sub category created with ID -> %s", stl)
|
|
2200
|
-
return True
|
|
2201
|
-
self.logger.info("Creating sub category -> '%s'", name)
|
|
2751
|
+
request_url = self.get_cases_list_url()
|
|
2202
2752
|
|
|
2203
|
-
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
|
+
)
|
|
2204
2761
|
|
|
2205
2762
|
# end method definition
|
|
2206
2763
|
|
|
2207
|
-
def
|
|
2208
|
-
|
|
2209
|
-
name: str,
|
|
2210
|
-
index: int,
|
|
2211
|
-
category_resp_dict: list,
|
|
2212
|
-
) -> int:
|
|
2213
|
-
"""Verify sub category entity instance id already exists.
|
|
2764
|
+
def get_case_by_name(self, name: str) -> dict | None:
|
|
2765
|
+
"""Get case instance by its name.
|
|
2214
2766
|
|
|
2215
2767
|
Args:
|
|
2216
2768
|
name (str):
|
|
2217
|
-
The name of the
|
|
2218
|
-
index (int):
|
|
2219
|
-
TODO: add description
|
|
2220
|
-
category_resp_dict (list):
|
|
2221
|
-
TODO: add description!
|
|
2769
|
+
The name of the case.
|
|
2222
2770
|
|
|
2223
2771
|
Returns:
|
|
2224
|
-
|
|
2225
|
-
Returns
|
|
2772
|
+
dict | None:
|
|
2773
|
+
Returns the category item or None if a category with the given name does not exist.
|
|
2226
2774
|
|
|
2227
2775
|
"""
|
|
2228
2776
|
|
|
2229
|
-
|
|
2230
|
-
names = [item["Properties"]["Name"] for item in subcategoy_resp_dict["_embedded"]["SubCategory"]]
|
|
2231
|
-
stl = 0
|
|
2232
|
-
if name in names:
|
|
2233
|
-
self.logger.info("Sub category record -> '%s' already exists", name)
|
|
2234
|
-
for item in subcategoy_resp_dict["_embedded"]["SubCategory"]:
|
|
2235
|
-
stl = item["Identity"]["Id"]
|
|
2236
|
-
self.logger.info("Sub category created with ID -> %s", stl)
|
|
2237
|
-
return stl
|
|
2777
|
+
categories = self.get_cases()
|
|
2238
2778
|
|
|
2239
|
-
return
|
|
2779
|
+
return self.get_result_item(response=categories, entity_type="AllCasesList", key="Name", value=name)
|
|
2240
2780
|
|
|
2241
2781
|
# end method definition
|
|
2242
2782
|
|
|
2243
|
-
def create_users_from_config_file(self, otawpsection: str,
|
|
2783
|
+
def create_users_from_config_file(self, otawpsection: str, otds_object: OTDS) -> None:
|
|
2244
2784
|
"""Read user information from customizer file and call create user method.
|
|
2245
2785
|
|
|
2246
2786
|
Args:
|
|
2247
2787
|
otawpsection (str):
|
|
2248
|
-
|
|
2249
|
-
|
|
2788
|
+
Payload section for AppWorks.
|
|
2789
|
+
otds_object (OTDS):
|
|
2250
2790
|
The OTDS object.
|
|
2251
2791
|
|
|
2252
2792
|
Returns:
|
|
@@ -2259,7 +2799,7 @@ class OTAWP:
|
|
|
2259
2799
|
users = otds.get("users", [])
|
|
2260
2800
|
if users is not None:
|
|
2261
2801
|
for user in users:
|
|
2262
|
-
|
|
2802
|
+
otds_object.add_user(
|
|
2263
2803
|
user.get("partition"),
|
|
2264
2804
|
user.get("name"),
|
|
2265
2805
|
user.get("description"),
|
|
@@ -2270,32 +2810,32 @@ class OTAWP:
|
|
|
2270
2810
|
roles = otds.get("roles", [])
|
|
2271
2811
|
if roles is not None:
|
|
2272
2812
|
for role in roles:
|
|
2273
|
-
|
|
2813
|
+
otds_object.add_user_to_group(
|
|
2274
2814
|
user.get("name") + "@" + user.get("partition"),
|
|
2275
2815
|
role.get("name"),
|
|
2276
2816
|
)
|
|
2277
2817
|
else:
|
|
2278
|
-
self.logger.
|
|
2279
|
-
"
|
|
2818
|
+
self.logger.warning(
|
|
2819
|
+
"Roles section not in payload for AppWorks users.",
|
|
2280
2820
|
)
|
|
2281
2821
|
else:
|
|
2282
2822
|
self.logger.error(
|
|
2283
|
-
"
|
|
2823
|
+
"User section not in payload for AppWorks users.",
|
|
2284
2824
|
)
|
|
2285
2825
|
else:
|
|
2286
2826
|
self.logger.error(
|
|
2287
|
-
"
|
|
2827
|
+
"OTDS section not in payload for AppWorks users.",
|
|
2288
2828
|
)
|
|
2289
2829
|
|
|
2290
2830
|
# end method definition
|
|
2291
2831
|
|
|
2292
|
-
def create_roles_from_config_file(self, otawpsection: str,
|
|
2832
|
+
def create_roles_from_config_file(self, otawpsection: str, otds_object: OTDS) -> None:
|
|
2293
2833
|
"""Read grop information from customizer file and call create grop method.
|
|
2294
2834
|
|
|
2295
2835
|
Args:
|
|
2296
2836
|
otawpsection (str):
|
|
2297
|
-
|
|
2298
|
-
|
|
2837
|
+
Payload section for AppWorks.
|
|
2838
|
+
otds_object (OTDS):
|
|
2299
2839
|
The OTDS object used to access the OTDS REST API.
|
|
2300
2840
|
|
|
2301
2841
|
Returns:
|
|
@@ -2309,56 +2849,23 @@ class OTAWP:
|
|
|
2309
2849
|
if roles is not None:
|
|
2310
2850
|
for role in roles:
|
|
2311
2851
|
# Add new group if it does not yet exist:
|
|
2312
|
-
if not
|
|
2313
|
-
|
|
2852
|
+
if not otds_object.get_group(group=role.get("name"), show_error=False):
|
|
2853
|
+
otds_object.add_group(
|
|
2314
2854
|
role.get("partition"),
|
|
2315
2855
|
role.get("name"),
|
|
2316
2856
|
role.get("description"),
|
|
2317
2857
|
)
|
|
2318
2858
|
else:
|
|
2319
2859
|
self.logger.error(
|
|
2320
|
-
"
|
|
2860
|
+
"Roles section not in payload for AppWorks roles/groups.",
|
|
2321
2861
|
)
|
|
2322
2862
|
else:
|
|
2323
2863
|
self.logger.error(
|
|
2324
|
-
"
|
|
2864
|
+
"OTDS section not in payload for AppWorks roles/groups.",
|
|
2325
2865
|
)
|
|
2326
2866
|
|
|
2327
2867
|
# end method definition
|
|
2328
2868
|
|
|
2329
|
-
def create_loanruntime_from_config_file(self, platform: str) -> None:
|
|
2330
|
-
"""Verify flag and call loan_management_runtime().
|
|
2331
|
-
|
|
2332
|
-
Args:
|
|
2333
|
-
platform (str):
|
|
2334
|
-
YAML block related to platform.
|
|
2335
|
-
|
|
2336
|
-
Returns:
|
|
2337
|
-
None
|
|
2338
|
-
|
|
2339
|
-
"""
|
|
2340
|
-
|
|
2341
|
-
runtime = platform.get("runtime", {})
|
|
2342
|
-
if runtime is not None:
|
|
2343
|
-
app_names = runtime.get("appNames", [])
|
|
2344
|
-
if app_names is not None:
|
|
2345
|
-
for app_name in app_names:
|
|
2346
|
-
if app_name == "loanManagement":
|
|
2347
|
-
self.loan_management_runtime()
|
|
2348
|
-
else:
|
|
2349
|
-
self.logger.error(
|
|
2350
|
-
"Verifying runtime section: loanManagement not exits in yaml entry",
|
|
2351
|
-
)
|
|
2352
|
-
else:
|
|
2353
|
-
self.logger.error(
|
|
2354
|
-
"Verifying runtime section: App name section is empty in yaml",
|
|
2355
|
-
)
|
|
2356
|
-
else:
|
|
2357
|
-
self.logger.error(
|
|
2358
|
-
"Verifying runtime section: Runtime section is empty in yaml",
|
|
2359
|
-
)
|
|
2360
|
-
|
|
2361
|
-
# end method definition
|
|
2362
2869
|
def create_cws_config(
|
|
2363
2870
|
self,
|
|
2364
2871
|
partition: str,
|
|
@@ -2368,18 +2875,21 @@ class OTAWP:
|
|
|
2368
2875
|
"""Create a workspace configuration in CWS.
|
|
2369
2876
|
|
|
2370
2877
|
Args:
|
|
2371
|
-
partition (str):
|
|
2372
|
-
|
|
2373
|
-
|
|
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.
|
|
2374
2884
|
|
|
2375
2885
|
Returns:
|
|
2376
|
-
dict | None:
|
|
2886
|
+
dict | None:
|
|
2887
|
+
Response dictionary if successful, or None if the request fails.
|
|
2377
2888
|
|
|
2378
2889
|
"""
|
|
2379
|
-
self.logger.info("Creating CWS config for partition '%s' and resource '%s'...", partition, resource_name)
|
|
2380
2890
|
|
|
2381
2891
|
# Construct the SOAP request body
|
|
2382
|
-
|
|
2892
|
+
cws_config_data = f"""
|
|
2383
2893
|
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
2384
2894
|
<SOAP:Header>
|
|
2385
2895
|
<header xmlns="http://schemas.cordys.com/General/1.0/">
|
|
@@ -2419,79 +2929,108 @@ class OTAWP:
|
|
|
2419
2929
|
</SOAP:Envelope>
|
|
2420
2930
|
"""
|
|
2421
2931
|
|
|
2422
|
-
retries = 0
|
|
2423
|
-
max_retries = 20 # Maximum retry limit
|
|
2424
2932
|
error_messages = [
|
|
2425
2933
|
"Collaborative Workspace Service Container is not able to handle the SOAP request",
|
|
2426
2934
|
"Service Group Lookup failure",
|
|
2427
2935
|
]
|
|
2428
2936
|
|
|
2429
|
-
|
|
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:
|
|
2430
2950
|
try:
|
|
2431
2951
|
response = requests.post(
|
|
2432
|
-
url=
|
|
2433
|
-
data=
|
|
2434
|
-
headers=
|
|
2952
|
+
url=request_url,
|
|
2953
|
+
data=cws_config_data,
|
|
2954
|
+
headers=REQUEST_HEADERS_XML,
|
|
2435
2955
|
cookies=self.cookie(),
|
|
2436
2956
|
timeout=None,
|
|
2437
2957
|
)
|
|
2438
2958
|
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
self.logger.info("CWS config created successfully.")
|
|
2451
|
-
return response.text
|
|
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
|
|
2452
2970
|
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
self.
|
|
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)
|
|
2457
2981
|
retries += 1
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
if "Object has been changed by other user" in response.text:
|
|
2462
|
-
self.logger.info("CWS config already exists")
|
|
2463
|
-
return response.text
|
|
2982
|
+
else:
|
|
2983
|
+
self.logger.info("Successfully created CWS configuration.")
|
|
2984
|
+
return self.parse_xml(response.text)
|
|
2464
2985
|
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
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)
|
|
2468
2990
|
retries += 1
|
|
2991
|
+
continue
|
|
2469
2992
|
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
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:
|
|
2473
3004
|
|
|
2474
3005
|
# Log when retries are exhausted
|
|
2475
3006
|
self.logger.error("Retry limit exceeded. CWS config creation failed.")
|
|
2476
3007
|
return None
|
|
2477
3008
|
|
|
2478
3009
|
# end method definition
|
|
3010
|
+
|
|
2479
3011
|
def verify_user_having_role(self, organization: str, user_name: str, role_name: str) -> bool:
|
|
2480
|
-
"""Verify
|
|
3012
|
+
"""Verify that the user has the specified role.
|
|
2481
3013
|
|
|
2482
3014
|
Args:
|
|
2483
|
-
organization(str):
|
|
2484
|
-
|
|
2485
|
-
|
|
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.
|
|
2486
3021
|
|
|
2487
3022
|
Returns:
|
|
2488
|
-
bool:
|
|
3023
|
+
bool:
|
|
3024
|
+
True if the user has the role, False if not, or None if request fails.
|
|
2489
3025
|
|
|
2490
3026
|
"""
|
|
2491
|
-
|
|
3027
|
+
|
|
3028
|
+
self.logger.info(
|
|
3029
|
+
"Verify user -> '%s' has role -> '%s' in organization -> '%s'...", user_name, role_name, organization
|
|
3030
|
+
)
|
|
2492
3031
|
|
|
2493
3032
|
# Construct the SOAP request body
|
|
2494
|
-
|
|
3033
|
+
user_role_data = f"""
|
|
2495
3034
|
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
2496
3035
|
<SOAP:Header xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
2497
3036
|
<header xmlns="http://schemas.cordys.com/General/1.0/">
|
|
@@ -2516,69 +3055,89 @@ class OTAWP:
|
|
|
2516
3055
|
"""
|
|
2517
3056
|
|
|
2518
3057
|
retries = 0
|
|
2519
|
-
|
|
2520
|
-
while retries <
|
|
3058
|
+
|
|
3059
|
+
while retries < REQUEST_MAX_RETRIES:
|
|
2521
3060
|
try:
|
|
2522
3061
|
response = requests.post(
|
|
2523
3062
|
url=self.gateway_url(),
|
|
2524
|
-
data=
|
|
2525
|
-
headers=
|
|
3063
|
+
data=user_role_data,
|
|
3064
|
+
headers=REQUEST_HEADERS_XML,
|
|
2526
3065
|
cookies=self.cookie(),
|
|
2527
3066
|
timeout=None,
|
|
2528
3067
|
)
|
|
2529
3068
|
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
else:
|
|
2538
|
-
self.logger.info("Verifyied User '%s' not having role '%s'.", user_name, role_name)
|
|
2539
|
-
return False
|
|
2540
|
-
# Handle session expiration
|
|
2541
|
-
if response.status_code == 401 and retries == 0:
|
|
2542
|
-
self.logger.warning("Session expired. Re-authenticating...")
|
|
2543
|
-
self.authenticate(revalidate=True)
|
|
2544
|
-
retries += 1
|
|
2545
|
-
continue
|
|
2546
|
-
|
|
2547
|
-
# Log errors for failed requests
|
|
2548
|
-
self.logger.error("Failed to verify user role: %s", response.text)
|
|
2549
|
-
time.sleep(60)
|
|
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
|
+
)
|
|
2550
3076
|
retries += 1
|
|
3077
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
3078
|
+
self.logger.info("Retrying... Attempt %d/%d", retries, REQUEST_MAX_RETRIES)
|
|
3079
|
+
continue
|
|
2551
3080
|
|
|
2552
|
-
|
|
2553
|
-
|
|
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)
|
|
2554
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)
|
|
2555
3107
|
|
|
2556
3108
|
# Log when retries are exhausted
|
|
2557
3109
|
self.logger.error("Retry limit exceeded. User role verification failed.")
|
|
3110
|
+
|
|
2558
3111
|
return False # Return False if the retries limit is exceeded
|
|
2559
3112
|
|
|
2560
3113
|
# end method definition
|
|
2561
3114
|
|
|
2562
|
-
def
|
|
2563
|
-
"""Assign
|
|
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.
|
|
2564
3117
|
|
|
2565
3118
|
Args:
|
|
2566
|
-
organization (str):
|
|
2567
|
-
|
|
2568
|
-
|
|
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.
|
|
2569
3125
|
|
|
2570
3126
|
Returns:
|
|
2571
|
-
bool:
|
|
3127
|
+
bool:
|
|
3128
|
+
True if the user received the role, False otherwise.
|
|
2572
3129
|
|
|
2573
3130
|
"""
|
|
2574
|
-
self.logger.info(
|
|
3131
|
+
self.logger.info(
|
|
3132
|
+
"Assign role -> '%s' to user -> '%s' in organization -> '%s'...", role_name, user_name, organization
|
|
3133
|
+
)
|
|
2575
3134
|
|
|
2576
3135
|
# Check if user already has the role before making the request
|
|
2577
3136
|
if self.verify_user_having_role(organization, user_name, role_name):
|
|
2578
3137
|
return True
|
|
2579
3138
|
|
|
2580
3139
|
# Construct the SOAP request body
|
|
2581
|
-
|
|
3140
|
+
developer_role_data = f"""\
|
|
2582
3141
|
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
2583
3142
|
<SOAP:Header xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
2584
3143
|
<header xmlns="http://schemas.cordys.com/General/1.0/">
|
|
@@ -2620,7 +3179,7 @@ class OTAWP:
|
|
|
2620
3179
|
<string>cn=everyoneIn{organization},cn=organizational roles,o={organization},cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2621
3180
|
<string>cn=Administrator,cn=Cordys@Work,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2622
3181
|
<string>cn=OTDS Push Service,cn=OpenText OTDS Platform Push Connector,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2623
|
-
<string>cn=
|
|
3182
|
+
<string>cn={role_name},cn=Cordys@Work,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2624
3183
|
</role>
|
|
2625
3184
|
<description>
|
|
2626
3185
|
<string>{user_name}</string>
|
|
@@ -2643,22 +3202,31 @@ class OTAWP:
|
|
|
2643
3202
|
</SOAP:Body>
|
|
2644
3203
|
</SOAP:Envelope>
|
|
2645
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
|
+
|
|
2646
3216
|
retries = 0
|
|
2647
|
-
max_retries = 6
|
|
2648
|
-
retry_delay = 30 # Time in seconds
|
|
2649
3217
|
|
|
2650
|
-
while retries <
|
|
3218
|
+
while retries < REQUEST_MAX_RETRIES:
|
|
2651
3219
|
try:
|
|
2652
3220
|
response = requests.post(
|
|
2653
|
-
url=
|
|
2654
|
-
data=
|
|
2655
|
-
headers=
|
|
3221
|
+
url=request_url,
|
|
3222
|
+
data=developer_role_data,
|
|
3223
|
+
headers=REQUEST_HEADERS_XML,
|
|
2656
3224
|
cookies=self.cookie(),
|
|
2657
|
-
timeout=
|
|
3225
|
+
timeout=REQUEST_TIMEOUT,
|
|
2658
3226
|
)
|
|
2659
3227
|
|
|
2660
3228
|
if response.ok and role_name in response.text:
|
|
2661
|
-
self.logger.info("
|
|
3229
|
+
self.logger.info("Successfully assigned the role -> '%s' to user -> '%s'.", role_name, user_name)
|
|
2662
3230
|
return True
|
|
2663
3231
|
|
|
2664
3232
|
# Handle session expiration
|
|
@@ -2670,18 +3238,19 @@ class OTAWP:
|
|
|
2670
3238
|
|
|
2671
3239
|
# Log failure response
|
|
2672
3240
|
self.logger.error(
|
|
2673
|
-
"Failed to assign role to user '%s'
|
|
3241
|
+
"Failed to assign role -> '%s' to user -> '%s'; error -> %s (%s)",
|
|
3242
|
+
role_name,
|
|
2674
3243
|
user_name,
|
|
2675
3244
|
response.status_code,
|
|
2676
3245
|
response.text,
|
|
2677
3246
|
)
|
|
2678
3247
|
|
|
2679
|
-
except requests.RequestException:
|
|
2680
|
-
self.logger.error("Request failed
|
|
3248
|
+
except requests.RequestException as req_exception:
|
|
3249
|
+
self.logger.error("Request failed; error -> %s", str(req_exception))
|
|
2681
3250
|
|
|
2682
3251
|
retries += 1
|
|
2683
|
-
self.logger.info("Retrying... Attempt %d/%d", retries,
|
|
2684
|
-
time.sleep(
|
|
3252
|
+
self.logger.info("Retrying... Attempt %d/%d", retries, REQUEST_MAX_RETRIES)
|
|
3253
|
+
time.sleep(REQUEST_RETRY_DELAY)
|
|
2685
3254
|
|
|
2686
3255
|
self.logger.error("Retry limit exceeded. Role assignment failed for user '%s'.", user_name)
|
|
2687
3256
|
return False
|