pyxecm 1.5__py3-none-any.whl → 2.0.0__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 +6 -2
- pyxecm/avts.py +1492 -0
- pyxecm/coreshare.py +1075 -960
- pyxecm/customizer/__init__.py +16 -4
- pyxecm/customizer/__main__.py +58 -0
- pyxecm/customizer/api/__init__.py +5 -0
- pyxecm/customizer/api/__main__.py +6 -0
- pyxecm/customizer/api/app.py +914 -0
- pyxecm/customizer/api/auth.py +154 -0
- pyxecm/customizer/api/metrics.py +92 -0
- pyxecm/customizer/api/models.py +13 -0
- pyxecm/customizer/api/payload_list.py +865 -0
- pyxecm/customizer/api/settings.py +103 -0
- pyxecm/customizer/browser_automation.py +332 -139
- pyxecm/customizer/customizer.py +1075 -1057
- pyxecm/customizer/exceptions.py +35 -0
- pyxecm/customizer/guidewire.py +322 -0
- pyxecm/customizer/k8s.py +787 -338
- pyxecm/customizer/log.py +107 -0
- pyxecm/customizer/m365.py +3424 -2270
- pyxecm/customizer/nhc.py +1169 -0
- pyxecm/customizer/openapi.py +258 -0
- pyxecm/customizer/payload.py +18201 -7030
- pyxecm/customizer/pht.py +1047 -210
- pyxecm/customizer/salesforce.py +836 -727
- pyxecm/customizer/sap.py +58 -41
- pyxecm/customizer/servicenow.py +851 -383
- pyxecm/customizer/settings.py +442 -0
- pyxecm/customizer/successfactors.py +408 -346
- pyxecm/customizer/translate.py +83 -48
- pyxecm/helper/__init__.py +5 -2
- pyxecm/helper/assoc.py +98 -38
- pyxecm/helper/data.py +2482 -742
- pyxecm/helper/logadapter.py +27 -0
- pyxecm/helper/web.py +229 -101
- pyxecm/helper/xml.py +528 -172
- pyxecm/maintenance_page/__init__.py +5 -0
- pyxecm/maintenance_page/__main__.py +6 -0
- pyxecm/maintenance_page/app.py +51 -0
- pyxecm/maintenance_page/settings.py +28 -0
- pyxecm/maintenance_page/static/favicon.avif +0 -0
- pyxecm/maintenance_page/templates/maintenance.html +165 -0
- pyxecm/otac.py +234 -140
- pyxecm/otawp.py +2689 -0
- pyxecm/otcs.py +12344 -7547
- pyxecm/otds.py +3166 -2219
- pyxecm/otiv.py +36 -21
- pyxecm/otmm.py +1363 -296
- pyxecm/otpd.py +231 -127
- pyxecm-2.0.0.dist-info/METADATA +145 -0
- pyxecm-2.0.0.dist-info/RECORD +54 -0
- {pyxecm-1.5.dist-info → pyxecm-2.0.0.dist-info}/WHEEL +1 -1
- pyxecm-1.5.dist-info/METADATA +0 -51
- pyxecm-1.5.dist-info/RECORD +0 -30
- {pyxecm-1.5.dist-info → pyxecm-2.0.0.dist-info/licenses}/LICENSE +0 -0
- {pyxecm-1.5.dist-info → pyxecm-2.0.0.dist-info}/top_level.txt +0 -0
pyxecm/otawp.py
ADDED
|
@@ -0,0 +1,2689 @@
|
|
|
1
|
+
"""Synchronize AppWorks projects, publsh and create run time instances for that."""
|
|
2
|
+
|
|
3
|
+
__author__ = "Dr. Marc Diefenbruch"
|
|
4
|
+
__copyright__ = "Copyright (C) 2024-2025, OpenText"
|
|
5
|
+
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
6
|
+
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
7
|
+
__email__ = "mdiefenb@opentext.com"
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import logging
|
|
11
|
+
import re
|
|
12
|
+
import time
|
|
13
|
+
import uuid
|
|
14
|
+
import xml.etree.ElementTree as ET
|
|
15
|
+
|
|
16
|
+
import requests
|
|
17
|
+
|
|
18
|
+
from pyxecm.otds import OTDS
|
|
19
|
+
|
|
20
|
+
default_logger = logging.getLogger("pyxecm.otawp")
|
|
21
|
+
|
|
22
|
+
REQUEST_HEADERS = {
|
|
23
|
+
"Content-Type": "text/xml; charset=utf-8",
|
|
24
|
+
"accept": "application/xml",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
REQUEST_FORM_HEADERS = {
|
|
28
|
+
"accept": "application/xml;charset=utf-8",
|
|
29
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
REQUEST_HEADERS_JSON = {
|
|
33
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
34
|
+
"accept": "application/json",
|
|
35
|
+
}
|
|
36
|
+
REQUEST_TIMEOUT = 120
|
|
37
|
+
SYNC_PUBLISH_REQUEST_TIMEOUT = 300
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class OTAWP:
|
|
41
|
+
"""Class OTRAWP is used to automate settings in OpenText AppWorks Platform (OTAWP)."""
|
|
42
|
+
|
|
43
|
+
logger: logging.Logger = default_logger
|
|
44
|
+
|
|
45
|
+
_config: dict
|
|
46
|
+
_config = None
|
|
47
|
+
_cookie = None
|
|
48
|
+
_otawp_ticket = None
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
protocol: str,
|
|
53
|
+
hostname: str,
|
|
54
|
+
port: int,
|
|
55
|
+
username: str | None = None,
|
|
56
|
+
password: str | None = None,
|
|
57
|
+
otawp_ticket: str | None = None,
|
|
58
|
+
otcs_partition_name: str | None = None,
|
|
59
|
+
otds_admin_partition_mame: str | None = None,
|
|
60
|
+
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
|
+
license_file: str | None = None,
|
|
66
|
+
product_name: str | None = None,
|
|
67
|
+
product_description: str | None = None,
|
|
68
|
+
logger: logging.Logger = default_logger,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Initialize OTAWP (AppWorks Platform) object.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
protocol (str): #TODO _description_
|
|
74
|
+
hostname (str): #TODO _description_
|
|
75
|
+
port (int): #TODO _description_
|
|
76
|
+
username (str | None, optional): #TODO _description_. Defaults to None.
|
|
77
|
+
password (str | None, optional): #TODO _description_. Defaults to None.
|
|
78
|
+
otawp_ticket (str | None, optional): #TODO _description_. Defaults to None.
|
|
79
|
+
otcs_partition_name (str | None, optional): #TODO _description_. Defaults to None.
|
|
80
|
+
otds_admin_partition_mame (str | None, optional): #TODO _description_. Defaults to None.
|
|
81
|
+
config_map_name (str | None, optional): #TODO _description_. Defaults to None.
|
|
82
|
+
otcs_resource_id (str | None, optional): #TODO _description_. Defaults to None.
|
|
83
|
+
otds_url (str | None, optional): #TODO _description_. Defaults to None.
|
|
84
|
+
otcs_url (str | None, optional): #TODO _description_. Defaults to None.
|
|
85
|
+
otcs_base_path (str | None, optional): #TODO _description_. Defaults to None.
|
|
86
|
+
license_file (str | None, optional): #TODO _description_. Defaults to None.
|
|
87
|
+
product_name (str | None, optional): #TODO _description_. Defaults to None.
|
|
88
|
+
product_description (str | None, optional): #TODO _description_. Defaults to None.
|
|
89
|
+
logger (logging.Logger, optional): #TODO: _description_. Defaults to default_logger.
|
|
90
|
+
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
if logger != default_logger:
|
|
94
|
+
self.logger = logger.getChild("otawp")
|
|
95
|
+
for logfilter in logger.filters:
|
|
96
|
+
self.logger.addFilter(logfilter)
|
|
97
|
+
|
|
98
|
+
otawp_config = {}
|
|
99
|
+
|
|
100
|
+
otawp_config["hostname"] = hostname if hostname else "appworks"
|
|
101
|
+
otawp_config["protocol"] = protocol if protocol else "http"
|
|
102
|
+
otawp_config["port"] = port if port else 8080
|
|
103
|
+
otawp_config["username"] = username if username else "sysadmin"
|
|
104
|
+
otawp_config["password"] = password if password else ""
|
|
105
|
+
otawp_config["otcs_partition_name"] = otcs_partition_name if otcs_partition_name else ""
|
|
106
|
+
otawp_config["otds_admin_partition_mame"] = otds_admin_partition_mame if otds_admin_partition_mame else ""
|
|
107
|
+
otawp_config["config_map_name"] = config_map_name if config_map_name else ""
|
|
108
|
+
otawp_config["otcs_resource_id"] = otcs_resource_id if otcs_resource_id else ""
|
|
109
|
+
otawp_config["otds_url"] = otds_url if otds_url else ""
|
|
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"] = (
|
|
115
|
+
product_description if product_description else "OpenText Appworks Platform"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if otawp_ticket:
|
|
119
|
+
self._cookie = {"defaultinst_SAMLart": otawp_ticket}
|
|
120
|
+
|
|
121
|
+
server = "{}://{}".format(protocol, otawp_config["hostname"])
|
|
122
|
+
if str(port) not in ["80", "443"]:
|
|
123
|
+
server += f":{port}"
|
|
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
|
+
)
|
|
138
|
+
|
|
139
|
+
otawp_config["createPriority"] = (
|
|
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
|
+
)
|
|
179
|
+
|
|
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
|
+
self._config = otawp_config
|
|
188
|
+
|
|
189
|
+
# end method definition
|
|
190
|
+
|
|
191
|
+
def server(self) -> str:
|
|
192
|
+
"""Return server information.
|
|
193
|
+
|
|
194
|
+
Returns:
|
|
195
|
+
str:
|
|
196
|
+
Server configuration.
|
|
197
|
+
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
return self.config()["server"]
|
|
201
|
+
|
|
202
|
+
# end method definition
|
|
203
|
+
|
|
204
|
+
def set_organization(self, organization: str) -> None:
|
|
205
|
+
"""Set the organization context.
|
|
206
|
+
|
|
207
|
+
Args:
|
|
208
|
+
organization (str):
|
|
209
|
+
Organization name.
|
|
210
|
+
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
otawp_base_url = f"/home/{organization}"
|
|
214
|
+
otawp_url = self.server() + otawp_base_url
|
|
215
|
+
ldap_root = (
|
|
216
|
+
f"com.eibus.web.soap.Gateway.wcp?organization=o={organization},cn=cordys,cn=defaultInst,o=opentext.net"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Assign to self._config if that's where you store configuration data
|
|
220
|
+
self._config["gatewayAuthenticationUrl"] = otawp_url + f"/com.eibus.web.soap.Gateway.wcp?{ldap_root}"
|
|
221
|
+
|
|
222
|
+
self._config["soapGatewayUrl"] = otawp_url + f"/com.eibus.web.soap.Gateway.wcp?{ldap_root}&defaultinst_ct=abcd"
|
|
223
|
+
|
|
224
|
+
self.logger.info("Organization set to '%s'.", organization)
|
|
225
|
+
|
|
226
|
+
# end method definition
|
|
227
|
+
|
|
228
|
+
def baseurl(self) -> str:
|
|
229
|
+
"""Return the configuration dictionary.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
str:
|
|
233
|
+
Base URL of AppWorks Platform.
|
|
234
|
+
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
return self.config()["baseurl"]
|
|
238
|
+
|
|
239
|
+
# end method definition
|
|
240
|
+
|
|
241
|
+
def license_file(self) -> str:
|
|
242
|
+
"""Return the license_file.
|
|
243
|
+
|
|
244
|
+
Returns:
|
|
245
|
+
str:
|
|
246
|
+
Returns license_file
|
|
247
|
+
|
|
248
|
+
"""
|
|
249
|
+
|
|
250
|
+
return self.config()["license_file"]
|
|
251
|
+
|
|
252
|
+
# end method definition
|
|
253
|
+
|
|
254
|
+
def product_name(self) -> str:
|
|
255
|
+
"""Return the product_name.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
str:
|
|
259
|
+
Returns product_name
|
|
260
|
+
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
return self.config()["product_name"]
|
|
264
|
+
|
|
265
|
+
# end method definition
|
|
266
|
+
|
|
267
|
+
def product_description(self) -> str:
|
|
268
|
+
"""Return the product_description.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
str:
|
|
272
|
+
Returns product_description
|
|
273
|
+
|
|
274
|
+
"""
|
|
275
|
+
|
|
276
|
+
return self.config()["product_description"]
|
|
277
|
+
|
|
278
|
+
# end method definition
|
|
279
|
+
|
|
280
|
+
def hostname(self) -> str:
|
|
281
|
+
"""Return hostname.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
str: Returns hostname
|
|
285
|
+
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
return self.config()["hostname"]
|
|
289
|
+
|
|
290
|
+
def username(self) -> str:
|
|
291
|
+
"""Return username.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
str: Returns username
|
|
295
|
+
|
|
296
|
+
"""
|
|
297
|
+
return self.config()["username"]
|
|
298
|
+
|
|
299
|
+
# end method definition
|
|
300
|
+
|
|
301
|
+
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.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
str:
|
|
328
|
+
Returns OTDS admin partition mame.
|
|
329
|
+
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
return self.config()["otds_admin_partition_mame"]
|
|
333
|
+
|
|
334
|
+
# end method definition
|
|
335
|
+
|
|
336
|
+
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.
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
str:
|
|
377
|
+
Returns otds url
|
|
378
|
+
|
|
379
|
+
"""
|
|
380
|
+
return self.config()["otds_url"]
|
|
381
|
+
|
|
382
|
+
# end method definition
|
|
383
|
+
|
|
384
|
+
def otcs_base_path(self) -> str:
|
|
385
|
+
"""Return the OTCS base path.
|
|
386
|
+
|
|
387
|
+
Returns:
|
|
388
|
+
str: Returns otcs base path
|
|
389
|
+
|
|
390
|
+
"""
|
|
391
|
+
return self.config()["otcs_base_path"]
|
|
392
|
+
|
|
393
|
+
# end method definition
|
|
394
|
+
|
|
395
|
+
def config(self) -> dict:
|
|
396
|
+
"""Return the configuration dictionary.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
dict: Configuration dictionary
|
|
400
|
+
|
|
401
|
+
"""
|
|
402
|
+
|
|
403
|
+
return self._config
|
|
404
|
+
|
|
405
|
+
# end method definition
|
|
406
|
+
|
|
407
|
+
def cookie(self) -> dict:
|
|
408
|
+
"""Return the login cookie of OTAWP.
|
|
409
|
+
|
|
410
|
+
This is set by the authenticate() method
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
dict:
|
|
414
|
+
OTAWP cookie
|
|
415
|
+
|
|
416
|
+
"""
|
|
417
|
+
|
|
418
|
+
return self._cookie
|
|
419
|
+
|
|
420
|
+
# end method definition
|
|
421
|
+
|
|
422
|
+
def credentials(self) -> str:
|
|
423
|
+
"""Return the SOAP payload with credentials (username and password).
|
|
424
|
+
|
|
425
|
+
Returns:
|
|
426
|
+
str:
|
|
427
|
+
SOAP payload with username and password.
|
|
428
|
+
|
|
429
|
+
"""
|
|
430
|
+
|
|
431
|
+
username = self.config()["username"]
|
|
432
|
+
password = self.config()["password"]
|
|
433
|
+
|
|
434
|
+
soap_payload = f"""
|
|
435
|
+
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
436
|
+
<SOAP:Header>
|
|
437
|
+
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
|
|
438
|
+
<wsse:UsernameToken xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
|
|
439
|
+
<wsse:Username>{username}</wsse:Username>
|
|
440
|
+
<wsse:Password>{password}</wsse:Password>
|
|
441
|
+
</wsse:UsernameToken>
|
|
442
|
+
<i18n:international xmlns:i18n="http://www.w3.org/2005/09/ws-i18n">
|
|
443
|
+
<locale xmlns="http://www.w3.org/2005/09/ws-i18n">en-US</locale>
|
|
444
|
+
</i18n:international>
|
|
445
|
+
</wsse:Security>
|
|
446
|
+
</SOAP:Header>
|
|
447
|
+
<SOAP:Body>
|
|
448
|
+
<samlp:Request xmlns:samlp="urn:oasis:names:tc:SAML:1.0:protocol" MajorVersion="1" MinorVersion="1">
|
|
449
|
+
<samlp:AuthenticationQuery>
|
|
450
|
+
<saml:Subject xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion">
|
|
451
|
+
<saml:NameIdentifier Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">{username}</saml:NameIdentifier>
|
|
452
|
+
</saml:Subject>
|
|
453
|
+
</samlp:AuthenticationQuery>
|
|
454
|
+
</samlp:Request>
|
|
455
|
+
</SOAP:Body>
|
|
456
|
+
</SOAP:Envelope>
|
|
457
|
+
"""
|
|
458
|
+
|
|
459
|
+
return soap_payload
|
|
460
|
+
|
|
461
|
+
# end method definition
|
|
462
|
+
|
|
463
|
+
def credential_url(self) -> str:
|
|
464
|
+
"""Return the Credentials URL of OTAWP.
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
str: Credentials URL
|
|
468
|
+
|
|
469
|
+
"""
|
|
470
|
+
|
|
471
|
+
return self.config()["gatewayAuthenticationUrl"]
|
|
472
|
+
|
|
473
|
+
# end method definition
|
|
474
|
+
|
|
475
|
+
def gateway_url(self) -> str:
|
|
476
|
+
"""Return SOAP gateway URL of OTAWP.
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
str:
|
|
480
|
+
The SOAP gateway URL.
|
|
481
|
+
|
|
482
|
+
"""
|
|
483
|
+
|
|
484
|
+
return self.config()["soapGatewayUrl"]
|
|
485
|
+
|
|
486
|
+
# end method definition
|
|
487
|
+
|
|
488
|
+
def create_priority_url(self) -> str:
|
|
489
|
+
"""Return create priority URL of OTAWP.
|
|
490
|
+
|
|
491
|
+
Returns:
|
|
492
|
+
str: createPriority URL
|
|
493
|
+
|
|
494
|
+
"""
|
|
495
|
+
|
|
496
|
+
return self.config()["createPriority"]
|
|
497
|
+
|
|
498
|
+
# end method definition
|
|
499
|
+
|
|
500
|
+
def get_all_priorities_url(self) -> str:
|
|
501
|
+
"""Return get all priorities URL of OTAWP.
|
|
502
|
+
|
|
503
|
+
Returns:
|
|
504
|
+
str:
|
|
505
|
+
The getAllPriorities URL of OTAWP.
|
|
506
|
+
|
|
507
|
+
"""
|
|
508
|
+
|
|
509
|
+
return self.config()["getAllPriorities"]
|
|
510
|
+
|
|
511
|
+
# end method definition
|
|
512
|
+
|
|
513
|
+
def create_customer_url(self) -> str:
|
|
514
|
+
"""Return create customer URL of OTAWP.
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
str:
|
|
518
|
+
The create customer URL.
|
|
519
|
+
|
|
520
|
+
"""
|
|
521
|
+
|
|
522
|
+
return self.config()["createCustomer"]
|
|
523
|
+
|
|
524
|
+
# end method definition
|
|
525
|
+
|
|
526
|
+
def get_all_customeres_url(self) -> str:
|
|
527
|
+
"""Return get all customers URL of OTAWP.
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
str:
|
|
531
|
+
The get all customers URL.
|
|
532
|
+
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
return self.config()["getAllCustomers"]
|
|
536
|
+
|
|
537
|
+
# end method definition
|
|
538
|
+
|
|
539
|
+
def create_casetype_url(self) -> str:
|
|
540
|
+
"""Return create case type URL of OTAWP.
|
|
541
|
+
|
|
542
|
+
Returns:
|
|
543
|
+
str:
|
|
544
|
+
The create case type URL.
|
|
545
|
+
|
|
546
|
+
"""
|
|
547
|
+
|
|
548
|
+
return self.config()["createCaseType"]
|
|
549
|
+
|
|
550
|
+
# end method definition
|
|
551
|
+
|
|
552
|
+
def get_all_case_types_url(self) -> str:
|
|
553
|
+
"""Return get all case types URL of OTAWP.
|
|
554
|
+
|
|
555
|
+
Returns:
|
|
556
|
+
str:
|
|
557
|
+
The get all case types URL.
|
|
558
|
+
|
|
559
|
+
"""
|
|
560
|
+
|
|
561
|
+
return self.config()["getAllCaseTypes"]
|
|
562
|
+
|
|
563
|
+
# end method definition
|
|
564
|
+
|
|
565
|
+
def create_category_url(self) -> str:
|
|
566
|
+
"""Return create category URL of OTAWP.
|
|
567
|
+
|
|
568
|
+
Returns:
|
|
569
|
+
str:
|
|
570
|
+
The create category URL.
|
|
571
|
+
|
|
572
|
+
"""
|
|
573
|
+
|
|
574
|
+
return self.config()["createCategory"]
|
|
575
|
+
|
|
576
|
+
# end method definition
|
|
577
|
+
|
|
578
|
+
def get_all_categories_url(self) -> str:
|
|
579
|
+
"""Return the get all categories URL of OTAWP.
|
|
580
|
+
|
|
581
|
+
Returns:
|
|
582
|
+
str:
|
|
583
|
+
The get all categories URL.
|
|
584
|
+
|
|
585
|
+
"""
|
|
586
|
+
|
|
587
|
+
return self.config()["getAllCategories"]
|
|
588
|
+
|
|
589
|
+
# end method definition
|
|
590
|
+
|
|
591
|
+
def get_all_loans_url(self) -> str:
|
|
592
|
+
"""Return get all loans URL of OTAWP.
|
|
593
|
+
|
|
594
|
+
Returns:
|
|
595
|
+
str:
|
|
596
|
+
The get all loans URL.
|
|
597
|
+
|
|
598
|
+
"""
|
|
599
|
+
|
|
600
|
+
return self.config()["getAllLoans"]
|
|
601
|
+
|
|
602
|
+
# end method definition
|
|
603
|
+
|
|
604
|
+
def remove_namespace(self, tag: str) -> str:
|
|
605
|
+
"""Remove namespace from XML tag."""
|
|
606
|
+
|
|
607
|
+
return tag.split("}", 1)[-1]
|
|
608
|
+
|
|
609
|
+
# end method definition
|
|
610
|
+
|
|
611
|
+
def parse_xml(self, xml_string: str) -> dict:
|
|
612
|
+
"""Parse XML string and return a dictionary without namespaces."""
|
|
613
|
+
|
|
614
|
+
def element_to_dict(element) -> dict: # noqa: ANN001
|
|
615
|
+
"""Convert XML element to dictionary."""
|
|
616
|
+
tag = self.remove_namespace(element.tag)
|
|
617
|
+
children = list(element)
|
|
618
|
+
if children:
|
|
619
|
+
return {
|
|
620
|
+
tag: {self.remove_namespace(child.tag): element_to_dict(child) for child in children},
|
|
621
|
+
}
|
|
622
|
+
return {tag: element.text.strip() if element.text else None}
|
|
623
|
+
|
|
624
|
+
root = ET.fromstring(xml_string)
|
|
625
|
+
|
|
626
|
+
return element_to_dict(root)
|
|
627
|
+
|
|
628
|
+
# end method definition
|
|
629
|
+
|
|
630
|
+
def find_key(self, data: dict | list, target_key: str) -> str:
|
|
631
|
+
"""Recursively search for a key in a nested dictionary and return its value.
|
|
632
|
+
|
|
633
|
+
Args:
|
|
634
|
+
data (dict | list):
|
|
635
|
+
TODO: _description_
|
|
636
|
+
target_key (str):
|
|
637
|
+
TODO: _description_
|
|
638
|
+
|
|
639
|
+
Returns:
|
|
640
|
+
_type_: _description_
|
|
641
|
+
|
|
642
|
+
"""
|
|
643
|
+
|
|
644
|
+
if isinstance(data, dict):
|
|
645
|
+
if target_key in data:
|
|
646
|
+
return data[target_key]
|
|
647
|
+
for value in data.values():
|
|
648
|
+
result = self.find_key(value, target_key)
|
|
649
|
+
if result is not None:
|
|
650
|
+
return result
|
|
651
|
+
elif isinstance(data, list):
|
|
652
|
+
for item in data:
|
|
653
|
+
result = self.find_key(item, target_key)
|
|
654
|
+
if result is not None:
|
|
655
|
+
return result
|
|
656
|
+
|
|
657
|
+
return None
|
|
658
|
+
|
|
659
|
+
# end method definition
|
|
660
|
+
|
|
661
|
+
def parse_request_response(
|
|
662
|
+
self,
|
|
663
|
+
response_object: object,
|
|
664
|
+
additional_error_message: str = "",
|
|
665
|
+
show_error: bool = True,
|
|
666
|
+
) -> dict | None:
|
|
667
|
+
"""Convert the text property of a request response object to a Python dict in a safe way.
|
|
668
|
+
|
|
669
|
+
Properly handle exceptions.
|
|
670
|
+
|
|
671
|
+
AppWorks may produce corrupt response when it gets restarted
|
|
672
|
+
or hitting resource limits. So we try to avoid a fatal error and bail
|
|
673
|
+
out more gracefully.
|
|
674
|
+
|
|
675
|
+
Args:
|
|
676
|
+
response_object (object):
|
|
677
|
+
This is reponse object delivered by the request call.
|
|
678
|
+
additional_error_message (str):
|
|
679
|
+
Print a custom error message.
|
|
680
|
+
show_error (bool):
|
|
681
|
+
If True log an error, if False log a warning.
|
|
682
|
+
|
|
683
|
+
Returns:
|
|
684
|
+
dict:
|
|
685
|
+
Response or None in case of an error.
|
|
686
|
+
|
|
687
|
+
"""
|
|
688
|
+
|
|
689
|
+
if not response_object:
|
|
690
|
+
return None
|
|
691
|
+
|
|
692
|
+
try:
|
|
693
|
+
dict_object = json.loads(response_object.text)
|
|
694
|
+
except json.JSONDecodeError as exception:
|
|
695
|
+
if additional_error_message:
|
|
696
|
+
message = "Cannot decode response as JSon. {}; error -> {}".format(
|
|
697
|
+
additional_error_message,
|
|
698
|
+
exception,
|
|
699
|
+
)
|
|
700
|
+
else:
|
|
701
|
+
message = "Cannot decode response as JSon; error -> {}".format(
|
|
702
|
+
exception,
|
|
703
|
+
)
|
|
704
|
+
if show_error:
|
|
705
|
+
self.logger.error(message)
|
|
706
|
+
else:
|
|
707
|
+
self.logger.warning(message)
|
|
708
|
+
return None
|
|
709
|
+
return dict_object
|
|
710
|
+
|
|
711
|
+
# end method definition
|
|
712
|
+
|
|
713
|
+
def authenticate(self, revalidate: bool = False) -> dict | None:
|
|
714
|
+
"""Authenticate at AppWorks.
|
|
715
|
+
|
|
716
|
+
Args:
|
|
717
|
+
revalidate (bool, optional):
|
|
718
|
+
Determine if a re-authentication is enforced
|
|
719
|
+
(e.g. if session has timed out with 401 error).
|
|
720
|
+
|
|
721
|
+
Returns:
|
|
722
|
+
dict:
|
|
723
|
+
Cookie information. Also stores cookie information in self._cookie
|
|
724
|
+
|
|
725
|
+
"""
|
|
726
|
+
|
|
727
|
+
self.logger.info("SAMLart generation started")
|
|
728
|
+
if self._cookie and not revalidate:
|
|
729
|
+
self.logger.debug(
|
|
730
|
+
"Session still valid - return existing cookie -> %s",
|
|
731
|
+
str(self._cookie),
|
|
732
|
+
)
|
|
733
|
+
return self._cookie
|
|
734
|
+
|
|
735
|
+
otawp_ticket = "NotSet"
|
|
736
|
+
|
|
737
|
+
response = None
|
|
738
|
+
try:
|
|
739
|
+
self.credentials()
|
|
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.")
|
|
753
|
+
return None
|
|
754
|
+
|
|
755
|
+
if response.ok:
|
|
756
|
+
self.logger.info("SAMLart generated successfully")
|
|
757
|
+
authenticate_dict = self.parse_xml(response.text)
|
|
758
|
+
if not authenticate_dict:
|
|
759
|
+
return None
|
|
760
|
+
assertion_artifact_dict = self.find_key(
|
|
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
|
|
776
|
+
|
|
777
|
+
return self._cookie
|
|
778
|
+
|
|
779
|
+
# end method definition
|
|
780
|
+
|
|
781
|
+
def create_workspace(self, workspace_name: str, workspace_id: str) -> dict | None:
|
|
782
|
+
"""Create a workspace in cws.
|
|
783
|
+
|
|
784
|
+
Args:
|
|
785
|
+
workspace_name (str):
|
|
786
|
+
The name of the workspace.
|
|
787
|
+
workspace_id (str):
|
|
788
|
+
The ID of the workspace.
|
|
789
|
+
|
|
790
|
+
Returns:
|
|
791
|
+
dict | None:
|
|
792
|
+
Response dictionary or error text
|
|
793
|
+
|
|
794
|
+
"""
|
|
795
|
+
|
|
796
|
+
self.logger.info(
|
|
797
|
+
"Create workspace -> '%s' (%s)...",
|
|
798
|
+
workspace_name,
|
|
799
|
+
workspace_id,
|
|
800
|
+
)
|
|
801
|
+
unique_id = uuid.uuid4()
|
|
802
|
+
|
|
803
|
+
license_post_body_json = f"""<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
804
|
+
<SOAP:Body>
|
|
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>"""
|
|
886
|
+
|
|
887
|
+
retries = 0
|
|
888
|
+
while True:
|
|
889
|
+
response = requests.post(
|
|
890
|
+
url=self.gateway_url(),
|
|
891
|
+
data=license_post_body_json,
|
|
892
|
+
headers=REQUEST_HEADERS,
|
|
893
|
+
cookies=self.cookie(),
|
|
894
|
+
timeout=REQUEST_TIMEOUT,
|
|
895
|
+
)
|
|
896
|
+
if response.ok:
|
|
897
|
+
self.logger.info(
|
|
898
|
+
"Successfully created workspace -> '%s' with ID -> %s",
|
|
899
|
+
workspace_name,
|
|
900
|
+
workspace_id,
|
|
901
|
+
)
|
|
902
|
+
return response.text
|
|
903
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
904
|
+
if response.status_code == 401 and retries == 0:
|
|
905
|
+
self.logger.warning("Session has expired - try to re-authenticate...")
|
|
906
|
+
self.authenticate(revalidate=True)
|
|
907
|
+
retries += 1
|
|
908
|
+
self.logger.error(response.text)
|
|
909
|
+
return response.text
|
|
910
|
+
|
|
911
|
+
# end method definition
|
|
912
|
+
|
|
913
|
+
def sync_workspace(self, workspace_name: str, workspace_id: str) -> dict | None:
|
|
914
|
+
"""Synchronize workspace.
|
|
915
|
+
|
|
916
|
+
Args:
|
|
917
|
+
workspace_name (str):
|
|
918
|
+
The name of the workspace.
|
|
919
|
+
workspace_id (str):
|
|
920
|
+
The ID of the workspace.
|
|
921
|
+
|
|
922
|
+
Returns:
|
|
923
|
+
dict | None:
|
|
924
|
+
Parsed response as a dictionary if successful, None otherwise.
|
|
925
|
+
|
|
926
|
+
"""
|
|
927
|
+
|
|
928
|
+
self.logger.info(
|
|
929
|
+
"Starting synchronization of workspace -> '%s'...",
|
|
930
|
+
workspace_name,
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
# SOAP request body
|
|
934
|
+
license_post_body_json = f"""
|
|
935
|
+
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
936
|
+
<SOAP:Body>
|
|
937
|
+
<Synchronize workspaceID="{workspace_id}" xmlns="http://schemas.cordys.com/cws/synchronize/1.0">
|
|
938
|
+
<DocumentID/>
|
|
939
|
+
<Asynchronous>false</Asynchronous>
|
|
940
|
+
</Synchronize>
|
|
941
|
+
</SOAP:Body>
|
|
942
|
+
</SOAP:Envelope>
|
|
943
|
+
"""
|
|
944
|
+
|
|
945
|
+
retries = 0
|
|
946
|
+
max_retries = 6
|
|
947
|
+
retry_delay = 60
|
|
948
|
+
|
|
949
|
+
while retries < max_retries:
|
|
950
|
+
try:
|
|
951
|
+
response = requests.post(
|
|
952
|
+
url=self.gateway_url(),
|
|
953
|
+
data=license_post_body_json,
|
|
954
|
+
headers=REQUEST_HEADERS,
|
|
955
|
+
cookies=self.cookie(),
|
|
956
|
+
timeout=SYNC_PUBLISH_REQUEST_TIMEOUT,
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
if response.ok:
|
|
960
|
+
self.logger.info(
|
|
961
|
+
"Workspace -> '%s' synchronized successfully.",
|
|
962
|
+
workspace_name,
|
|
963
|
+
)
|
|
964
|
+
return self.parse_xml(response.text)
|
|
965
|
+
|
|
966
|
+
if response.status_code == 401:
|
|
967
|
+
self.logger.warning("Session expired. Re-authenticating...")
|
|
968
|
+
self.authenticate(revalidate=True)
|
|
969
|
+
retries += 1
|
|
970
|
+
continue
|
|
971
|
+
|
|
972
|
+
if "faultcode" in response.text or "FaultDetails" in response.text:
|
|
973
|
+
self.logger.warning("SOAP fault occurred: %s", response.text)
|
|
974
|
+
retries += 1
|
|
975
|
+
time.sleep(retry_delay)
|
|
976
|
+
continue
|
|
977
|
+
|
|
978
|
+
self.logger.error("Unexpected error during sync: %s", response.text)
|
|
979
|
+
time.sleep(retry_delay)
|
|
980
|
+
retries += 1
|
|
981
|
+
|
|
982
|
+
except requests.RequestException:
|
|
983
|
+
self.logger.error("Sync failed with error. Proceeding with retry...")
|
|
984
|
+
time.sleep(retry_delay)
|
|
985
|
+
retries += 1
|
|
986
|
+
|
|
987
|
+
self.logger.error(
|
|
988
|
+
"Synchronization failed for workspace -> '%s' after %d retries.",
|
|
989
|
+
workspace_name,
|
|
990
|
+
retries,
|
|
991
|
+
)
|
|
992
|
+
return None
|
|
993
|
+
|
|
994
|
+
# end method definition
|
|
995
|
+
|
|
996
|
+
def publish_project(
|
|
997
|
+
self,
|
|
998
|
+
workspace_name: str,
|
|
999
|
+
project_name: str,
|
|
1000
|
+
workspace_id: str,
|
|
1001
|
+
project_id: str,
|
|
1002
|
+
) -> dict | bool:
|
|
1003
|
+
"""Publish the workspace project.
|
|
1004
|
+
|
|
1005
|
+
Args:
|
|
1006
|
+
workspace_name (str): The name of the workspace.
|
|
1007
|
+
project_name (str): The name of the project.
|
|
1008
|
+
workspace_id (str): The workspace ID.
|
|
1009
|
+
project_id (str): The project ID.
|
|
1010
|
+
|
|
1011
|
+
Returns:
|
|
1012
|
+
dict | bool: Request response (dictionary) if successful, False if it fails after retries.
|
|
1013
|
+
|
|
1014
|
+
"""
|
|
1015
|
+
|
|
1016
|
+
self.logger.info(
|
|
1017
|
+
"Publish project -> '%s' in workspace -> '%s'...",
|
|
1018
|
+
project_name,
|
|
1019
|
+
workspace_name,
|
|
1020
|
+
)
|
|
1021
|
+
|
|
1022
|
+
project_publish = f"""<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
1023
|
+
<SOAP:Body>
|
|
1024
|
+
<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
|
+
<object>
|
|
1026
|
+
<c:uri id="{project_id}"/>
|
|
1027
|
+
</object>
|
|
1028
|
+
</deployObject>
|
|
1029
|
+
</SOAP:Body>
|
|
1030
|
+
</SOAP:Envelope>"""
|
|
1031
|
+
|
|
1032
|
+
# Initialize retry parameters
|
|
1033
|
+
max_retries = 10
|
|
1034
|
+
retries = 0
|
|
1035
|
+
success_indicator = "deployObjectResponse"
|
|
1036
|
+
|
|
1037
|
+
while retries < max_retries:
|
|
1038
|
+
try:
|
|
1039
|
+
response = requests.post(
|
|
1040
|
+
url=self.gateway_url(),
|
|
1041
|
+
data=project_publish,
|
|
1042
|
+
headers=REQUEST_HEADERS,
|
|
1043
|
+
cookies=self.cookie(),
|
|
1044
|
+
timeout=SYNC_PUBLISH_REQUEST_TIMEOUT,
|
|
1045
|
+
)
|
|
1046
|
+
|
|
1047
|
+
# Check if the response is successful
|
|
1048
|
+
if response.ok:
|
|
1049
|
+
if success_indicator in response.text:
|
|
1050
|
+
self.logger.info(
|
|
1051
|
+
"Successfully published project -> '%s' in workspace -> '%s'",
|
|
1052
|
+
project_name,
|
|
1053
|
+
workspace_name,
|
|
1054
|
+
)
|
|
1055
|
+
return True
|
|
1056
|
+
else:
|
|
1057
|
+
self.logger.warning(
|
|
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)
|
|
1067
|
+
else:
|
|
1068
|
+
self.logger.error(
|
|
1069
|
+
"Unexpected error (status code %d). Retrying in 30 seconds... (Attempt %d of %d)",
|
|
1070
|
+
response.status_code,
|
|
1071
|
+
retries + 1,
|
|
1072
|
+
max_retries,
|
|
1073
|
+
)
|
|
1074
|
+
self.logger.error(
|
|
1075
|
+
"Error details: %s",
|
|
1076
|
+
response.text,
|
|
1077
|
+
)
|
|
1078
|
+
self.sync_workspace(workspace_name, workspace_id)
|
|
1079
|
+
retries += 1
|
|
1080
|
+
time.sleep(30)
|
|
1081
|
+
|
|
1082
|
+
except requests.RequestException:
|
|
1083
|
+
self.logger.error("Sync failed with error. Proceeding with retry...")
|
|
1084
|
+
retries += 1
|
|
1085
|
+
time.sleep(30)
|
|
1086
|
+
|
|
1087
|
+
# After reaching the maximum number of retries, log failure and return False
|
|
1088
|
+
self.logger.error(
|
|
1089
|
+
"Max retries reached. Failed to publish project -> '%s' in workspace -> '%s'.",
|
|
1090
|
+
project_name,
|
|
1091
|
+
workspace_name,
|
|
1092
|
+
)
|
|
1093
|
+
return False
|
|
1094
|
+
|
|
1095
|
+
# end method definition
|
|
1096
|
+
|
|
1097
|
+
def create_priority(self, name: str, description: str, status: int) -> dict | None:
|
|
1098
|
+
"""Create Priority entity instances.
|
|
1099
|
+
|
|
1100
|
+
Args:
|
|
1101
|
+
name (str): name
|
|
1102
|
+
description (str): description
|
|
1103
|
+
status (int): status
|
|
1104
|
+
|
|
1105
|
+
Returns:
|
|
1106
|
+
dict:
|
|
1107
|
+
Request response (dictionary) or None if the REST call fails
|
|
1108
|
+
|
|
1109
|
+
"""
|
|
1110
|
+
create_priority = {
|
|
1111
|
+
"Properties": {"Name": name, "Description": description, "Status": status},
|
|
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,
|
|
1203
|
+
},
|
|
1204
|
+
}
|
|
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
|
+
|
|
1240
|
+
"""
|
|
1241
|
+
|
|
1242
|
+
retries = 0
|
|
1243
|
+
while True:
|
|
1244
|
+
response = requests.get(
|
|
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)
|
|
1264
|
+
return None
|
|
1265
|
+
|
|
1266
|
+
# end method definition
|
|
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 = {
|
|
1285
|
+
"Properties": {"Name": name, "Description": description, "Status": status},
|
|
1286
|
+
}
|
|
1287
|
+
retries = 0
|
|
1288
|
+
while True:
|
|
1289
|
+
response = requests.post(
|
|
1290
|
+
url=self.create_casetype_url(),
|
|
1291
|
+
json=create_case_type,
|
|
1292
|
+
headers=REQUEST_HEADERS_JSON,
|
|
1293
|
+
cookies=self.cookie(),
|
|
1294
|
+
timeout=REQUEST_TIMEOUT,
|
|
1295
|
+
)
|
|
1296
|
+
if response.ok:
|
|
1297
|
+
self.logger.info("Case type created successfully")
|
|
1298
|
+
return self.parse_request_response(
|
|
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
|
|
1309
|
+
|
|
1310
|
+
# end method definition
|
|
1311
|
+
|
|
1312
|
+
def get_all_case_type(self) -> dict | None:
|
|
1313
|
+
"""Get all case type entty instances.
|
|
1314
|
+
|
|
1315
|
+
Args:
|
|
1316
|
+
None
|
|
1317
|
+
|
|
1318
|
+
Returns:
|
|
1319
|
+
dict:
|
|
1320
|
+
Request response (dictionary) or None if the REST call fails.
|
|
1321
|
+
|
|
1322
|
+
"""
|
|
1323
|
+
|
|
1324
|
+
retries = 0
|
|
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
|
|
1347
|
+
|
|
1348
|
+
# end method definition
|
|
1349
|
+
|
|
1350
|
+
def create_category(
|
|
1351
|
+
self,
|
|
1352
|
+
case_prefix: str,
|
|
1353
|
+
description: str,
|
|
1354
|
+
name: str,
|
|
1355
|
+
status: int,
|
|
1356
|
+
) -> dict | None:
|
|
1357
|
+
"""Create category entity instance.
|
|
1358
|
+
|
|
1359
|
+
Args:
|
|
1360
|
+
case_prefix (str):
|
|
1361
|
+
The prefix for the case.
|
|
1362
|
+
description (str):
|
|
1363
|
+
The description for the category.
|
|
1364
|
+
name (str):
|
|
1365
|
+
The name of the case.
|
|
1366
|
+
status (int):
|
|
1367
|
+
The status code.
|
|
1368
|
+
|
|
1369
|
+
Returns:
|
|
1370
|
+
dict:
|
|
1371
|
+
Request response (dictionary) or None if the REST call fails.
|
|
1372
|
+
|
|
1373
|
+
"""
|
|
1374
|
+
|
|
1375
|
+
create_categoty = {
|
|
1376
|
+
"Properties": {
|
|
1377
|
+
"CasePrefix": case_prefix,
|
|
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
|
|
1405
|
+
|
|
1406
|
+
# end method definition
|
|
1407
|
+
|
|
1408
|
+
def get_all_categories(self) -> dict | None:
|
|
1409
|
+
"""Get all categories entity intances.
|
|
1410
|
+
|
|
1411
|
+
Args:
|
|
1412
|
+
None
|
|
1413
|
+
Returns:
|
|
1414
|
+
dict:
|
|
1415
|
+
Request response (dictionary) or None if the REST call fails.
|
|
1416
|
+
|
|
1417
|
+
"""
|
|
1418
|
+
|
|
1419
|
+
retries = 0
|
|
1420
|
+
while True:
|
|
1421
|
+
response = requests.get(
|
|
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
|
|
1442
|
+
|
|
1443
|
+
# end method definition
|
|
1444
|
+
|
|
1445
|
+
def create_sub_categoy(
|
|
1446
|
+
self,
|
|
1447
|
+
name: str,
|
|
1448
|
+
description: str,
|
|
1449
|
+
status: int,
|
|
1450
|
+
parent_id: int,
|
|
1451
|
+
) -> dict | None:
|
|
1452
|
+
"""Create sub categoy entity instances.
|
|
1453
|
+
|
|
1454
|
+
Args:
|
|
1455
|
+
name (str):
|
|
1456
|
+
The name of the sub-category.
|
|
1457
|
+
description (str):
|
|
1458
|
+
The description for the sub-category.
|
|
1459
|
+
status (int):
|
|
1460
|
+
The status ID.
|
|
1461
|
+
parent_id (int):
|
|
1462
|
+
The parent ID of the category.
|
|
1463
|
+
|
|
1464
|
+
Returns:
|
|
1465
|
+
dict:
|
|
1466
|
+
Request response (dictionary) or None if the REST call fails.
|
|
1467
|
+
|
|
1468
|
+
"""
|
|
1469
|
+
|
|
1470
|
+
create_sub_categoty = {
|
|
1471
|
+
"Properties": {"Name": name, "Description": description, "Status": status},
|
|
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)
|
|
1497
|
+
return None
|
|
1498
|
+
|
|
1499
|
+
# end method definition
|
|
1500
|
+
|
|
1501
|
+
def get_all_sub_categeries(self, parent_id: int) -> dict | None:
|
|
1502
|
+
"""Get all sub categeries entity instances.
|
|
1503
|
+
|
|
1504
|
+
Args:
|
|
1505
|
+
parent_id (int):
|
|
1506
|
+
The parent ID of the sub categories.
|
|
1507
|
+
|
|
1508
|
+
Returns:
|
|
1509
|
+
dict:
|
|
1510
|
+
Request response (dictionary) or None if the REST call fails.
|
|
1511
|
+
|
|
1512
|
+
"""
|
|
1513
|
+
|
|
1514
|
+
retries = 0
|
|
1515
|
+
while True:
|
|
1516
|
+
base_url = self.baseurl()
|
|
1517
|
+
endpoint = "/app/entityRestService/api/OpentextCaseManagement/entities/Category/items/"
|
|
1518
|
+
child_path = "/childEntities/SubCategory"
|
|
1519
|
+
response = requests.get(
|
|
1520
|
+
url=base_url + endpoint + str(parent_id) + child_path,
|
|
1521
|
+
headers=REQUEST_HEADERS_JSON,
|
|
1522
|
+
cookies=self.cookie(),
|
|
1523
|
+
timeout=REQUEST_TIMEOUT,
|
|
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
|
|
1540
|
+
|
|
1541
|
+
# end method definition
|
|
1542
|
+
|
|
1543
|
+
def create_loan(
|
|
1544
|
+
self,
|
|
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.
|
|
1556
|
+
|
|
1557
|
+
Args:
|
|
1558
|
+
subject (str): subject
|
|
1559
|
+
description (str): description
|
|
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>"""
|
|
1616
|
+
|
|
1617
|
+
retries = 0
|
|
1618
|
+
while True:
|
|
1619
|
+
response = requests.post(
|
|
1620
|
+
url=self.gateway_url(),
|
|
1621
|
+
data=create_loan,
|
|
1622
|
+
headers=REQUEST_HEADERS,
|
|
1623
|
+
cookies=self.cookie(),
|
|
1624
|
+
timeout=REQUEST_TIMEOUT,
|
|
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
|
|
1635
|
+
|
|
1636
|
+
# end method definition
|
|
1637
|
+
|
|
1638
|
+
def get_all_loan(self) -> dict | None:
|
|
1639
|
+
"""Get all loan entity instances.
|
|
1640
|
+
|
|
1641
|
+
Args:
|
|
1642
|
+
None
|
|
1643
|
+
|
|
1644
|
+
Returns:
|
|
1645
|
+
dict:
|
|
1646
|
+
Request response (dictionary) or None if the REST call fails.
|
|
1647
|
+
|
|
1648
|
+
"""
|
|
1649
|
+
|
|
1650
|
+
retries = 0
|
|
1651
|
+
while True:
|
|
1652
|
+
response = requests.get(
|
|
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
|
|
1674
|
+
|
|
1675
|
+
# end method definition
|
|
1676
|
+
|
|
1677
|
+
def validate_workspace_response(self, response: str, workspace_name: str) -> bool:
|
|
1678
|
+
"""Verify if the workspace exists or was created successfully.
|
|
1679
|
+
|
|
1680
|
+
Args:
|
|
1681
|
+
response (str): response to validate
|
|
1682
|
+
workspace_name (str): The name of the workspace.
|
|
1683
|
+
|
|
1684
|
+
Returns:
|
|
1685
|
+
bool: True if the workspace exists or was created successfully, else False.
|
|
1686
|
+
|
|
1687
|
+
"""
|
|
1688
|
+
|
|
1689
|
+
if "Object already exists" in response or "createWorkspaceResponse" in response:
|
|
1690
|
+
self.logger.info(
|
|
1691
|
+
"The workspace already exists or was created with the name -> '%s'",
|
|
1692
|
+
workspace_name,
|
|
1693
|
+
)
|
|
1694
|
+
return True
|
|
1695
|
+
|
|
1696
|
+
self.logger.info(
|
|
1697
|
+
"The workspace -> '%s' does not exist or was not created. Please verify configurtion!",
|
|
1698
|
+
workspace_name,
|
|
1699
|
+
)
|
|
1700
|
+
return False
|
|
1701
|
+
|
|
1702
|
+
# end method definition
|
|
1703
|
+
|
|
1704
|
+
def is_workspace_already_exists(self, response: str, workspace_name: str) -> bool:
|
|
1705
|
+
"""Verify if workspace exists.
|
|
1706
|
+
|
|
1707
|
+
Args:
|
|
1708
|
+
response (str):
|
|
1709
|
+
The response.
|
|
1710
|
+
workspace_name (str):
|
|
1711
|
+
The name of the workspace.
|
|
1712
|
+
|
|
1713
|
+
Returns:
|
|
1714
|
+
bool:
|
|
1715
|
+
Return True if workspace exist else return false.
|
|
1716
|
+
|
|
1717
|
+
"""
|
|
1718
|
+
|
|
1719
|
+
if "Object already exists" in response:
|
|
1720
|
+
self.logger.info(
|
|
1721
|
+
"The workspace already exists with the name -> '%s'",
|
|
1722
|
+
workspace_name,
|
|
1723
|
+
)
|
|
1724
|
+
return True
|
|
1725
|
+
self.logger.info(
|
|
1726
|
+
"The Workspace has been created with the name -> '%s'",
|
|
1727
|
+
workspace_name,
|
|
1728
|
+
)
|
|
1729
|
+
return False
|
|
1730
|
+
|
|
1731
|
+
# end method definition
|
|
1732
|
+
|
|
1733
|
+
def create_workspace_with_retry(
|
|
1734
|
+
self,
|
|
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.
|
|
1741
|
+
|
|
1742
|
+
Args:
|
|
1743
|
+
workspace_name (str):
|
|
1744
|
+
The workspace name.
|
|
1745
|
+
workspace_gui_id (str):
|
|
1746
|
+
The workspace GUI ID.
|
|
1747
|
+
|
|
1748
|
+
Returns:
|
|
1749
|
+
dict | None:
|
|
1750
|
+
The response of the workspace creation or None in case an error occured.
|
|
1751
|
+
|
|
1752
|
+
"""
|
|
1753
|
+
|
|
1754
|
+
max_retries = 20 # Define the maximum number of retries
|
|
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
|
|
1776
|
+
|
|
1777
|
+
# After max retries, log and return the response or handle as needed
|
|
1778
|
+
self.logger.error(
|
|
1779
|
+
"Max retries reached for workspace -> '%s', unable to create successfully.",
|
|
1780
|
+
workspace_name,
|
|
1781
|
+
)
|
|
1782
|
+
return response
|
|
1783
|
+
|
|
1784
|
+
# end method definition
|
|
1785
|
+
|
|
1786
|
+
def loan_management_runtime(self) -> dict | None:
|
|
1787
|
+
"""Create all runtime objects for loan management application.
|
|
1788
|
+
|
|
1789
|
+
Args:
|
|
1790
|
+
None
|
|
1791
|
+
Returns:
|
|
1792
|
+
None
|
|
1793
|
+
|
|
1794
|
+
"""
|
|
1795
|
+
|
|
1796
|
+
self.logger.debug(" RUNTIME -->> Category instance creation started ........ ")
|
|
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")
|
|
1806
|
+
|
|
1807
|
+
############################# Sub category
|
|
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
|
+
)
|
|
1879
|
+
|
|
1880
|
+
if not self.verify_customer_exists("Interwoven, Inc"):
|
|
1881
|
+
self.create_customer(
|
|
1882
|
+
"Interwoven, Inc",
|
|
1883
|
+
"Interwoven, Inc",
|
|
1884
|
+
"Interwoven, Inc",
|
|
1885
|
+
)
|
|
1886
|
+
|
|
1887
|
+
if not self.verify_customer_exists("Jones Lang LaSalle"):
|
|
1888
|
+
self.create_customer(
|
|
1889
|
+
"Jones Lang LaSalle",
|
|
1890
|
+
"Jones Lang LaSalle",
|
|
1891
|
+
"Jones Lang LaSalle",
|
|
1892
|
+
)
|
|
1893
|
+
|
|
1894
|
+
if not self.verify_customer_exists("Key Point Consulting"):
|
|
1895
|
+
self.create_customer(
|
|
1896
|
+
"Key Point Consulting",
|
|
1897
|
+
"Key Point Consulting",
|
|
1898
|
+
"Key Point Consulting",
|
|
1899
|
+
)
|
|
1900
|
+
|
|
1901
|
+
customer_list = self.get_customer_lists()
|
|
1902
|
+
self.logger.debug(" RUNTIME -->> Customer instance creation ended")
|
|
1903
|
+
|
|
1904
|
+
######################################## PRIORITY
|
|
1905
|
+
self.logger.debug(" RUNTIME -->> priority instance creation started ........")
|
|
1906
|
+
prioity_list = []
|
|
1907
|
+
if not self.verify_priority_exists("High"):
|
|
1908
|
+
self.create_priority("High", "High", 1)
|
|
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
|
+
)
|
|
1939
|
+
|
|
1940
|
+
if loan_for_corporate_business in names:
|
|
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
|
+
)
|
|
1957
|
+
|
|
1958
|
+
if loan_for_business_loan_request in names:
|
|
1959
|
+
self.logger.info("Customer record Loan_for_business_Loan_Request exists")
|
|
1960
|
+
else:
|
|
1961
|
+
self.logger.info(
|
|
1962
|
+
"Creating customer Record with loan_for_business_loan_request",
|
|
1963
|
+
)
|
|
1964
|
+
response_dict = self.create_loan(
|
|
1965
|
+
subject=loan_for_business_loan_request,
|
|
1966
|
+
description=loan_for_business_loan_request,
|
|
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")
|
|
1976
|
+
|
|
1977
|
+
# end method definition
|
|
1978
|
+
|
|
1979
|
+
def get_category_lists(self) -> list:
|
|
1980
|
+
"""Get All category entty instances id's.
|
|
1981
|
+
|
|
1982
|
+
Args:
|
|
1983
|
+
None
|
|
1984
|
+
Returns:
|
|
1985
|
+
list: list of category IDs
|
|
1986
|
+
|
|
1987
|
+
"""
|
|
1988
|
+
|
|
1989
|
+
category_resp_dict = []
|
|
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)
|
|
1997
|
+
|
|
1998
|
+
return category_resp_dict
|
|
1999
|
+
|
|
2000
|
+
# end method definition
|
|
2001
|
+
|
|
2002
|
+
def get_case_type_lists(self) -> list:
|
|
2003
|
+
"""Get All CaseType entity instances IDs.
|
|
2004
|
+
|
|
2005
|
+
Args:
|
|
2006
|
+
None
|
|
2007
|
+
|
|
2008
|
+
Returns:
|
|
2009
|
+
list:
|
|
2010
|
+
List of all case type IDs.
|
|
2011
|
+
|
|
2012
|
+
"""
|
|
2013
|
+
|
|
2014
|
+
case_type_list = []
|
|
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)
|
|
2022
|
+
|
|
2023
|
+
return case_type_list
|
|
2024
|
+
|
|
2025
|
+
# end method definition
|
|
2026
|
+
|
|
2027
|
+
def get_customer_lists(self) -> list:
|
|
2028
|
+
"""Get all customer entity instances id's.
|
|
2029
|
+
|
|
2030
|
+
Args:
|
|
2031
|
+
None
|
|
2032
|
+
Returns:
|
|
2033
|
+
list:
|
|
2034
|
+
A list of all customer IDs.
|
|
2035
|
+
|
|
2036
|
+
"""
|
|
2037
|
+
|
|
2038
|
+
customer_list = []
|
|
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)
|
|
2046
|
+
|
|
2047
|
+
return customer_list
|
|
2048
|
+
|
|
2049
|
+
# end method definition
|
|
2050
|
+
|
|
2051
|
+
def get_priority_lists(self) -> list:
|
|
2052
|
+
"""Get all priority entity instances IDs.
|
|
2053
|
+
|
|
2054
|
+
Args:
|
|
2055
|
+
None
|
|
2056
|
+
Returns:
|
|
2057
|
+
list:
|
|
2058
|
+
A list with all priority IDs.
|
|
2059
|
+
|
|
2060
|
+
"""
|
|
2061
|
+
|
|
2062
|
+
prioity_list = []
|
|
2063
|
+
authenticate_dict = self.get_all_priorities()
|
|
2064
|
+
for item in authenticate_dict["_embedded"]["PriorityList"]:
|
|
2065
|
+
first_item_href = item["_links"]["item"]["href"]
|
|
2066
|
+
integer_value = int(re.search(r"\d+", first_item_href).group())
|
|
2067
|
+
self.logger.info("Priority created with ID -> %d", integer_value)
|
|
2068
|
+
prioity_list.append(integer_value)
|
|
2069
|
+
self.logger.info("All extracted priority IDs -> %s ", prioity_list)
|
|
2070
|
+
|
|
2071
|
+
return prioity_list
|
|
2072
|
+
|
|
2073
|
+
# end method definition
|
|
2074
|
+
|
|
2075
|
+
def verify_category_exists(self, name: str) -> bool:
|
|
2076
|
+
"""Verify category entity instance already exists.
|
|
2077
|
+
|
|
2078
|
+
Args:
|
|
2079
|
+
name (str):
|
|
2080
|
+
The name of the category.
|
|
2081
|
+
|
|
2082
|
+
Returns:
|
|
2083
|
+
bool:
|
|
2084
|
+
Returns True if already record exists with same name, else returns False.
|
|
2085
|
+
|
|
2086
|
+
"""
|
|
2087
|
+
|
|
2088
|
+
categoy_resp_dict = self.get_all_categories()
|
|
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)
|
|
2094
|
+
|
|
2095
|
+
return False
|
|
2096
|
+
|
|
2097
|
+
# end method definition
|
|
2098
|
+
|
|
2099
|
+
def vverify_case_type_exists(self, name: str) -> bool:
|
|
2100
|
+
"""Verify case type entity instance already exists.
|
|
2101
|
+
|
|
2102
|
+
Args:
|
|
2103
|
+
name (str):
|
|
2104
|
+
The name of the case type.
|
|
2105
|
+
|
|
2106
|
+
Returns:
|
|
2107
|
+
bool:
|
|
2108
|
+
Returns True if already record exists with same name, else returns False.
|
|
2109
|
+
|
|
2110
|
+
"""
|
|
2111
|
+
|
|
2112
|
+
casetype_resp_dict = self.get_all_case_type()
|
|
2113
|
+
names = [item["Properties"]["Name"] for item in casetype_resp_dict["_embedded"]["AllCaseTypes"]]
|
|
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)
|
|
2118
|
+
|
|
2119
|
+
return False
|
|
2120
|
+
|
|
2121
|
+
# end method definition
|
|
2122
|
+
|
|
2123
|
+
def verify_customer_exists(self, name: str) -> bool:
|
|
2124
|
+
"""Verify cusomer entty instance already exists.
|
|
2125
|
+
|
|
2126
|
+
Args:
|
|
2127
|
+
name (str):
|
|
2128
|
+
The name of the customer.
|
|
2129
|
+
|
|
2130
|
+
Returns:
|
|
2131
|
+
bool:
|
|
2132
|
+
Returns True if already record exists with same name, else returns False
|
|
2133
|
+
|
|
2134
|
+
"""
|
|
2135
|
+
|
|
2136
|
+
customer_resp_dict = self.get_all_customers()
|
|
2137
|
+
names = [item["Properties"]["CustomerName"] for item in customer_resp_dict["_embedded"]["CustomerList"]]
|
|
2138
|
+
if name in names:
|
|
2139
|
+
self.logger.info("Customer -> '%s' already exists", name)
|
|
2140
|
+
return True
|
|
2141
|
+
self.logger.info("Creating customer -> '%s'", name)
|
|
2142
|
+
return False
|
|
2143
|
+
|
|
2144
|
+
# end method definition
|
|
2145
|
+
|
|
2146
|
+
def verify_priority_exists(self, name: str) -> bool:
|
|
2147
|
+
"""Verify priority entity instance already exists.
|
|
2148
|
+
|
|
2149
|
+
Args:
|
|
2150
|
+
name (str):
|
|
2151
|
+
The name of the priority.
|
|
2152
|
+
|
|
2153
|
+
Returns:
|
|
2154
|
+
bool:
|
|
2155
|
+
Returns True if already record exists with same name, else returns False
|
|
2156
|
+
|
|
2157
|
+
"""
|
|
2158
|
+
|
|
2159
|
+
authenticate_dict = self.get_all_priorities()
|
|
2160
|
+
names = [item["Properties"]["Name"] for item in authenticate_dict["_embedded"]["PriorityList"]]
|
|
2161
|
+
if name in names:
|
|
2162
|
+
self.logger.info("Priority -> '%s' already exists", name)
|
|
2163
|
+
return True
|
|
2164
|
+
self.logger.info("Creating priority -> '%s'", name)
|
|
2165
|
+
|
|
2166
|
+
return False
|
|
2167
|
+
|
|
2168
|
+
# end method definition
|
|
2169
|
+
|
|
2170
|
+
def verify_sub_category_exists(
|
|
2171
|
+
self,
|
|
2172
|
+
name: str,
|
|
2173
|
+
index: int,
|
|
2174
|
+
category_resp_dict: list,
|
|
2175
|
+
) -> bool:
|
|
2176
|
+
"""Verify sub category entity instance already exists.
|
|
2177
|
+
|
|
2178
|
+
Args:
|
|
2179
|
+
name (str):
|
|
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
|
|
2185
|
+
|
|
2186
|
+
Returns:
|
|
2187
|
+
bool:
|
|
2188
|
+
Returns true if record already exists with same name, else returns false.
|
|
2189
|
+
|
|
2190
|
+
"""
|
|
2191
|
+
|
|
2192
|
+
subcategoy_resp_dict = self.get_all_sub_categeries(category_resp_dict[index])
|
|
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)
|
|
2202
|
+
|
|
2203
|
+
return False
|
|
2204
|
+
|
|
2205
|
+
# end method definition
|
|
2206
|
+
|
|
2207
|
+
def return_sub_category_exists_id(
|
|
2208
|
+
self,
|
|
2209
|
+
name: str,
|
|
2210
|
+
index: int,
|
|
2211
|
+
category_resp_dict: list,
|
|
2212
|
+
) -> int:
|
|
2213
|
+
"""Verify sub category entity instance id already exists.
|
|
2214
|
+
|
|
2215
|
+
Args:
|
|
2216
|
+
name (str):
|
|
2217
|
+
The name of the sub-category.
|
|
2218
|
+
index (int):
|
|
2219
|
+
TODO: add description
|
|
2220
|
+
category_resp_dict (list):
|
|
2221
|
+
TODO: add description!
|
|
2222
|
+
|
|
2223
|
+
Returns:
|
|
2224
|
+
bool:
|
|
2225
|
+
Returns true if record already exists with same name, else returns false.
|
|
2226
|
+
|
|
2227
|
+
"""
|
|
2228
|
+
|
|
2229
|
+
subcategoy_resp_dict = self.get_all_sub_categeries(category_resp_dict[index])
|
|
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
|
|
2238
|
+
|
|
2239
|
+
return None
|
|
2240
|
+
|
|
2241
|
+
# end method definition
|
|
2242
|
+
|
|
2243
|
+
def create_users_from_config_file(self, otawpsection: str, _otds: OTDS) -> None:
|
|
2244
|
+
"""Read user information from customizer file and call create user method.
|
|
2245
|
+
|
|
2246
|
+
Args:
|
|
2247
|
+
otawpsection (str):
|
|
2248
|
+
YAML block related to appworks
|
|
2249
|
+
_otds:
|
|
2250
|
+
The OTDS object.
|
|
2251
|
+
|
|
2252
|
+
Returns:
|
|
2253
|
+
None
|
|
2254
|
+
|
|
2255
|
+
"""
|
|
2256
|
+
|
|
2257
|
+
otds = otawpsection.get("otds", {})
|
|
2258
|
+
if otds is not None:
|
|
2259
|
+
users = otds.get("users", [])
|
|
2260
|
+
if users is not None:
|
|
2261
|
+
for user in users:
|
|
2262
|
+
_otds.add_user(
|
|
2263
|
+
user.get("partition"),
|
|
2264
|
+
user.get("name"),
|
|
2265
|
+
user.get("description"),
|
|
2266
|
+
user.get("first_name"),
|
|
2267
|
+
user.get("last_name"),
|
|
2268
|
+
user.get("email"),
|
|
2269
|
+
)
|
|
2270
|
+
roles = otds.get("roles", [])
|
|
2271
|
+
if roles is not None:
|
|
2272
|
+
for role in roles:
|
|
2273
|
+
_otds.add_user_to_group(
|
|
2274
|
+
user.get("name") + "@" + user.get("partition"),
|
|
2275
|
+
role.get("name"),
|
|
2276
|
+
)
|
|
2277
|
+
else:
|
|
2278
|
+
self.logger.error(
|
|
2279
|
+
"Verifying Users section: roles section not presented in yaml for otds users",
|
|
2280
|
+
)
|
|
2281
|
+
else:
|
|
2282
|
+
self.logger.error(
|
|
2283
|
+
"Verifying Users section: user section not presented in yaml",
|
|
2284
|
+
)
|
|
2285
|
+
else:
|
|
2286
|
+
self.logger.error(
|
|
2287
|
+
"Verifying Users section: otds section not presented in yaml",
|
|
2288
|
+
)
|
|
2289
|
+
|
|
2290
|
+
# end method definition
|
|
2291
|
+
|
|
2292
|
+
def create_roles_from_config_file(self, otawpsection: str, _otds: OTDS) -> None:
|
|
2293
|
+
"""Read grop information from customizer file and call create grop method.
|
|
2294
|
+
|
|
2295
|
+
Args:
|
|
2296
|
+
otawpsection (str):
|
|
2297
|
+
YAML block related to appworks.
|
|
2298
|
+
_otds (object):
|
|
2299
|
+
The OTDS object used to access the OTDS REST API.
|
|
2300
|
+
|
|
2301
|
+
Returns:
|
|
2302
|
+
None
|
|
2303
|
+
|
|
2304
|
+
"""
|
|
2305
|
+
|
|
2306
|
+
otds = otawpsection.get("otds", {})
|
|
2307
|
+
if otds is not None:
|
|
2308
|
+
roles = otds.get("roles", [])
|
|
2309
|
+
if roles is not None:
|
|
2310
|
+
for role in roles:
|
|
2311
|
+
# Add new group if it does not yet exist:
|
|
2312
|
+
if not _otds.get_group(group=role.get("name"), show_error=False):
|
|
2313
|
+
_otds.add_group(
|
|
2314
|
+
role.get("partition"),
|
|
2315
|
+
role.get("name"),
|
|
2316
|
+
role.get("description"),
|
|
2317
|
+
)
|
|
2318
|
+
else:
|
|
2319
|
+
self.logger.error(
|
|
2320
|
+
"Verifying roles section: roles section not presented in yaml",
|
|
2321
|
+
)
|
|
2322
|
+
else:
|
|
2323
|
+
self.logger.error(
|
|
2324
|
+
"Verifying roles section: otds section not presented in yaml",
|
|
2325
|
+
)
|
|
2326
|
+
|
|
2327
|
+
# end method definition
|
|
2328
|
+
|
|
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
|
+
def create_cws_config(
|
|
2363
|
+
self,
|
|
2364
|
+
partition: str,
|
|
2365
|
+
resource_name: str,
|
|
2366
|
+
otcs_url: str,
|
|
2367
|
+
) -> dict | None:
|
|
2368
|
+
"""Create a workspace configuration in CWS.
|
|
2369
|
+
|
|
2370
|
+
Args:
|
|
2371
|
+
partition (str): The partition name for the workspace.
|
|
2372
|
+
resource_name (str): The resource name.
|
|
2373
|
+
otcs_url (str): The OTCS endpoint URL.
|
|
2374
|
+
|
|
2375
|
+
Returns:
|
|
2376
|
+
dict | None: Response dictionary if successful, or None if the request fails.
|
|
2377
|
+
|
|
2378
|
+
"""
|
|
2379
|
+
self.logger.info("Creating CWS config for partition '%s' and resource '%s'...", partition, resource_name)
|
|
2380
|
+
|
|
2381
|
+
# Construct the SOAP request body
|
|
2382
|
+
cws_config_req_xml = f"""
|
|
2383
|
+
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
2384
|
+
<SOAP:Header>
|
|
2385
|
+
<header xmlns="http://schemas.cordys.com/General/1.0/">
|
|
2386
|
+
<Logger/>
|
|
2387
|
+
</header>
|
|
2388
|
+
<i18n:international xmlns:i18n="http://www.w3.org/2005/09/ws-i18n">
|
|
2389
|
+
<i18n:locale>en-US</i18n:locale>
|
|
2390
|
+
</i18n:international>
|
|
2391
|
+
</SOAP:Header>
|
|
2392
|
+
<SOAP:Body>
|
|
2393
|
+
<UpdateXMLObject xmlns="http://schemas.cordys.com/1.0/xmlstore">
|
|
2394
|
+
<tuple lastModified="{int(time.time() * 1000)}"
|
|
2395
|
+
key="/com/ot-ps/csws/otcs_ws_config.xml"
|
|
2396
|
+
level="organization"
|
|
2397
|
+
name="otcs_ws_config.xml"
|
|
2398
|
+
original="/com/ot-ps/csws/otcs_ws_config.xml"
|
|
2399
|
+
version="organization">
|
|
2400
|
+
<new>
|
|
2401
|
+
<CSWSConfig>
|
|
2402
|
+
<Partition>{partition}</Partition>
|
|
2403
|
+
<EndPointUrl>{otcs_url}/cws/services/Authentication</EndPointUrl>
|
|
2404
|
+
<Resources>
|
|
2405
|
+
<Resource type="Cordys">
|
|
2406
|
+
<Name>__OTDS#Shared#Platform#Resource__</Name>
|
|
2407
|
+
<Space>shared</Space>
|
|
2408
|
+
</Resource>
|
|
2409
|
+
<Resource type="OTCS">
|
|
2410
|
+
<Name>{resource_name}</Name>
|
|
2411
|
+
<Space>shared</Space>
|
|
2412
|
+
</Resource>
|
|
2413
|
+
</Resources>
|
|
2414
|
+
</CSWSConfig>
|
|
2415
|
+
</new>
|
|
2416
|
+
</tuple>
|
|
2417
|
+
</UpdateXMLObject>
|
|
2418
|
+
</SOAP:Body>
|
|
2419
|
+
</SOAP:Envelope>
|
|
2420
|
+
"""
|
|
2421
|
+
|
|
2422
|
+
retries = 0
|
|
2423
|
+
max_retries = 20 # Maximum retry limit
|
|
2424
|
+
error_messages = [
|
|
2425
|
+
"Collaborative Workspace Service Container is not able to handle the SOAP request",
|
|
2426
|
+
"Service Group Lookup failure",
|
|
2427
|
+
]
|
|
2428
|
+
|
|
2429
|
+
while retries < max_retries:
|
|
2430
|
+
try:
|
|
2431
|
+
response = requests.post(
|
|
2432
|
+
url=self.gateway_url(),
|
|
2433
|
+
data=cws_config_req_xml,
|
|
2434
|
+
headers=REQUEST_HEADERS,
|
|
2435
|
+
cookies=self.cookie(),
|
|
2436
|
+
timeout=None,
|
|
2437
|
+
)
|
|
2438
|
+
|
|
2439
|
+
# Handle successful response
|
|
2440
|
+
if response.ok:
|
|
2441
|
+
if any(error_message in response.text for error_message in error_messages):
|
|
2442
|
+
self.logger.warning(
|
|
2443
|
+
"Service error detected, retrying in 60 seconds... (Retry %d of %d)",
|
|
2444
|
+
retries + 1,
|
|
2445
|
+
max_retries,
|
|
2446
|
+
)
|
|
2447
|
+
time.sleep(60)
|
|
2448
|
+
retries += 1
|
|
2449
|
+
else:
|
|
2450
|
+
self.logger.info("CWS config created successfully.")
|
|
2451
|
+
return response.text
|
|
2452
|
+
|
|
2453
|
+
# Handle session expiration
|
|
2454
|
+
if response.status_code == 401 and retries == 0:
|
|
2455
|
+
self.logger.warning("Session expired. Re-authenticating...")
|
|
2456
|
+
self.authenticate(revalidate=True)
|
|
2457
|
+
retries += 1
|
|
2458
|
+
continue
|
|
2459
|
+
|
|
2460
|
+
# Handle case where object has been changed by another user
|
|
2461
|
+
if "Object has been changed by other user" in response.text:
|
|
2462
|
+
self.logger.info("CWS config already exists")
|
|
2463
|
+
return response.text
|
|
2464
|
+
|
|
2465
|
+
# Log errors for failed requests
|
|
2466
|
+
self.logger.error("Failed to create CWS config: %s", response.text)
|
|
2467
|
+
time.sleep(60)
|
|
2468
|
+
retries += 1
|
|
2469
|
+
|
|
2470
|
+
except requests.RequestException:
|
|
2471
|
+
self.logger.error("Request failed during CWS config creation")
|
|
2472
|
+
retries += 1
|
|
2473
|
+
|
|
2474
|
+
# Log when retries are exhausted
|
|
2475
|
+
self.logger.error("Retry limit exceeded. CWS config creation failed.")
|
|
2476
|
+
return None
|
|
2477
|
+
|
|
2478
|
+
# end method definition
|
|
2479
|
+
def verify_user_having_role(self, organization: str, user_name: str, role_name: str) -> bool:
|
|
2480
|
+
"""Verify if the user has the specified role.
|
|
2481
|
+
|
|
2482
|
+
Args:
|
|
2483
|
+
organization(str): organization name
|
|
2484
|
+
user_name (str): The username to verify.
|
|
2485
|
+
role_name (str): The role to check for the user.
|
|
2486
|
+
|
|
2487
|
+
Returns:
|
|
2488
|
+
bool: True if the user has the role, False if not, or None if request fails.
|
|
2489
|
+
|
|
2490
|
+
"""
|
|
2491
|
+
self.logger.info("Verifying user '%s' has role '%s' started.", user_name, role_name)
|
|
2492
|
+
|
|
2493
|
+
# Construct the SOAP request body
|
|
2494
|
+
cws_config_req_xml = f"""
|
|
2495
|
+
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
2496
|
+
<SOAP:Header xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
2497
|
+
<header xmlns="http://schemas.cordys.com/General/1.0/">
|
|
2498
|
+
<Logger xmlns="http://schemas.cordys.com/General/1.0/"/>
|
|
2499
|
+
</header>
|
|
2500
|
+
<i18n:international xmlns:i18n="http://www.w3.org/2005/09/ws-i18n">
|
|
2501
|
+
<i18n:locale>en-US</i18n:locale>
|
|
2502
|
+
</i18n:international>
|
|
2503
|
+
</SOAP:Header>
|
|
2504
|
+
<SOAP:Body>
|
|
2505
|
+
<SearchLDAP xmlns:xfr="http://schemas.cordys.com/1.0/xforms/runtime" xmlns="http://schemas.cordys.com/1.0/ldap">
|
|
2506
|
+
<dn xmlns="http://schemas.cordys.com/1.0/ldap">cn=organizational users,o={organization},cn=cordys,cn=defaultInst,o=opentext.net</dn>
|
|
2507
|
+
<scope xmlns="http://schemas.cordys.com/1.0/ldap">1</scope>
|
|
2508
|
+
<filter xmlns="http://schemas.cordys.com/1.0/ldap">&(objectclass=busorganizationaluser)(&(!(cn=SYSTEM))(!(cn=anonymous))(!(cn=wcpLicUser)))(|(description=*{user_name}*)(&(!(description=*))(cn=*{user_name}*)))</filter>
|
|
2509
|
+
<sort xmlns="http://schemas.cordys.com/1.0/ldap">ascending</sort>
|
|
2510
|
+
<sortBy xmlns="http://schemas.cordys.com/1.0/ldap"/>
|
|
2511
|
+
<returnValues xmlns="http://schemas.cordys.com/1.0/ldap">false</returnValues>
|
|
2512
|
+
<return xmlns="http://schemas.cordys.com/1.0/ldap"/>
|
|
2513
|
+
</SearchLDAP>
|
|
2514
|
+
</SOAP:Body>
|
|
2515
|
+
</SOAP:Envelope>
|
|
2516
|
+
"""
|
|
2517
|
+
|
|
2518
|
+
retries = 0
|
|
2519
|
+
max_retries = 6 # Maximum retry limit
|
|
2520
|
+
while retries < max_retries:
|
|
2521
|
+
try:
|
|
2522
|
+
response = requests.post(
|
|
2523
|
+
url=self.gateway_url(),
|
|
2524
|
+
data=cws_config_req_xml,
|
|
2525
|
+
headers=REQUEST_HEADERS,
|
|
2526
|
+
cookies=self.cookie(),
|
|
2527
|
+
timeout=None,
|
|
2528
|
+
)
|
|
2529
|
+
|
|
2530
|
+
# Handle successful response
|
|
2531
|
+
if response.ok:
|
|
2532
|
+
if (
|
|
2533
|
+
role_name in response.text
|
|
2534
|
+
): # Corrected syntax for checking if 'Developer' is in the response text
|
|
2535
|
+
self.logger.info("Verifyied user '%s' already has the role '%s'.", user_name, role_name)
|
|
2536
|
+
return True # Assuming the user has the role if the response contains 'Developer'
|
|
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)
|
|
2550
|
+
retries += 1
|
|
2551
|
+
|
|
2552
|
+
except requests.RequestException:
|
|
2553
|
+
self.logger.error("Request failed during user role verification")
|
|
2554
|
+
retries += 1
|
|
2555
|
+
|
|
2556
|
+
# Log when retries are exhausted
|
|
2557
|
+
self.logger.error("Retry limit exceeded. User role verification failed.")
|
|
2558
|
+
return False # Return False if the retries limit is exceeded
|
|
2559
|
+
|
|
2560
|
+
# end method definition
|
|
2561
|
+
|
|
2562
|
+
def assign_developer_role_to_user(self, organization: str, user_name: str, role_name: str) -> bool:
|
|
2563
|
+
"""Assign the 'Developer' role to the user and verify the role assignment.
|
|
2564
|
+
|
|
2565
|
+
Args:
|
|
2566
|
+
organization (str): The organization name.
|
|
2567
|
+
user_name (str): The username to verify.
|
|
2568
|
+
role_name (str): The role to be assigned.
|
|
2569
|
+
|
|
2570
|
+
Returns:
|
|
2571
|
+
bool: True if the user has the 'Developer' role, False otherwise.
|
|
2572
|
+
|
|
2573
|
+
"""
|
|
2574
|
+
self.logger.info("Assigning 'Developer' role to user '%s' in organization '%s'...", user_name, organization)
|
|
2575
|
+
|
|
2576
|
+
# Check if user already has the role before making the request
|
|
2577
|
+
if self.verify_user_having_role(organization, user_name, role_name):
|
|
2578
|
+
return True
|
|
2579
|
+
|
|
2580
|
+
# Construct the SOAP request body
|
|
2581
|
+
cws_config_req_xml = f"""\
|
|
2582
|
+
<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
2583
|
+
<SOAP:Header xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
|
|
2584
|
+
<header xmlns="http://schemas.cordys.com/General/1.0/">
|
|
2585
|
+
<Logger xmlns="http://schemas.cordys.com/General/1.0/"/>
|
|
2586
|
+
</header>
|
|
2587
|
+
<i18n:international xmlns:i18n="http://www.w3.org/2005/09/ws-i18n">
|
|
2588
|
+
<i18n:locale>en-US</i18n:locale>
|
|
2589
|
+
</i18n:international>
|
|
2590
|
+
</SOAP:Header>
|
|
2591
|
+
<SOAP:Body>
|
|
2592
|
+
<Update xmlns="http://schemas.cordys.com/1.0/ldap">
|
|
2593
|
+
<tuple>
|
|
2594
|
+
<old>
|
|
2595
|
+
<entry dn="cn=sysadmin,cn=organizational users,o={organization},cn=cordys,cn=defaultInst,o=opentext.net">
|
|
2596
|
+
<role>
|
|
2597
|
+
<string>cn=everyoneIn{organization},cn=organizational roles,o={organization},cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2598
|
+
<string>cn=Administrator,cn=Cordys@Work,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2599
|
+
<string>cn=OTDS Push Service,cn=OpenText OTDS Platform Push Connector,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2600
|
+
</role>
|
|
2601
|
+
<description>
|
|
2602
|
+
<string>{user_name}</string>
|
|
2603
|
+
</description>
|
|
2604
|
+
<cn>
|
|
2605
|
+
<string>{user_name}</string>
|
|
2606
|
+
</cn>
|
|
2607
|
+
<objectclass>
|
|
2608
|
+
<string>top</string>
|
|
2609
|
+
<string>busorganizationalobject</string>
|
|
2610
|
+
<string>busorganizationaluser</string>
|
|
2611
|
+
</objectclass>
|
|
2612
|
+
<authenticationuser>
|
|
2613
|
+
<string>cn={user_name},cn=authenticated users,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2614
|
+
</authenticationuser>
|
|
2615
|
+
</entry>
|
|
2616
|
+
</old>
|
|
2617
|
+
<new>
|
|
2618
|
+
<entry dn="cn={user_name},cn=organizational users,o={organization},cn=cordys,cn=defaultInst,o=opentext.net">
|
|
2619
|
+
<role>
|
|
2620
|
+
<string>cn=everyoneIn{organization},cn=organizational roles,o={organization},cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2621
|
+
<string>cn=Administrator,cn=Cordys@Work,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2622
|
+
<string>cn=OTDS Push Service,cn=OpenText OTDS Platform Push Connector,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2623
|
+
<string>cn=Developer,cn=Cordys@Work,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2624
|
+
</role>
|
|
2625
|
+
<description>
|
|
2626
|
+
<string>{user_name}</string>
|
|
2627
|
+
</description>
|
|
2628
|
+
<cn>
|
|
2629
|
+
<string>{user_name}</string>
|
|
2630
|
+
</cn>
|
|
2631
|
+
<objectclass>
|
|
2632
|
+
<string>top</string>
|
|
2633
|
+
<string>busorganizationalobject</string>
|
|
2634
|
+
<string>busorganizationaluser</string>
|
|
2635
|
+
</objectclass>
|
|
2636
|
+
<authenticationuser>
|
|
2637
|
+
<string>cn={user_name},cn=authenticated users,cn=cordys,cn=defaultInst,o=opentext.net</string>
|
|
2638
|
+
</authenticationuser>
|
|
2639
|
+
</entry>
|
|
2640
|
+
</new>
|
|
2641
|
+
</tuple>
|
|
2642
|
+
</Update>
|
|
2643
|
+
</SOAP:Body>
|
|
2644
|
+
</SOAP:Envelope>
|
|
2645
|
+
"""
|
|
2646
|
+
retries = 0
|
|
2647
|
+
max_retries = 6
|
|
2648
|
+
retry_delay = 30 # Time in seconds
|
|
2649
|
+
|
|
2650
|
+
while retries < max_retries:
|
|
2651
|
+
try:
|
|
2652
|
+
response = requests.post(
|
|
2653
|
+
url=self.gateway_url(),
|
|
2654
|
+
data=cws_config_req_xml,
|
|
2655
|
+
headers=REQUEST_HEADERS,
|
|
2656
|
+
cookies=self.cookie(),
|
|
2657
|
+
timeout=30,
|
|
2658
|
+
)
|
|
2659
|
+
|
|
2660
|
+
if response.ok and role_name in response.text:
|
|
2661
|
+
self.logger.info("User '%s' successfully assigned the '%s' role.", user_name, role_name)
|
|
2662
|
+
return True
|
|
2663
|
+
|
|
2664
|
+
# Handle session expiration
|
|
2665
|
+
if response.status_code == 401 and retries == 0:
|
|
2666
|
+
self.logger.warning("Session expired. Re-authenticating...")
|
|
2667
|
+
self.authenticate(revalidate=True)
|
|
2668
|
+
retries += 1
|
|
2669
|
+
continue # Retry immediately after re-authentication
|
|
2670
|
+
|
|
2671
|
+
# Log failure response
|
|
2672
|
+
self.logger.error(
|
|
2673
|
+
"Failed to assign role to user '%s': HTTP %s - %s",
|
|
2674
|
+
user_name,
|
|
2675
|
+
response.status_code,
|
|
2676
|
+
response.text,
|
|
2677
|
+
)
|
|
2678
|
+
|
|
2679
|
+
except requests.RequestException:
|
|
2680
|
+
self.logger.error("Request failed:")
|
|
2681
|
+
|
|
2682
|
+
retries += 1
|
|
2683
|
+
self.logger.info("Retrying... Attempt %d/%d", retries, max_retries)
|
|
2684
|
+
time.sleep(retry_delay)
|
|
2685
|
+
|
|
2686
|
+
self.logger.error("Retry limit exceeded. Role assignment failed for user '%s'.", user_name)
|
|
2687
|
+
return False
|
|
2688
|
+
|
|
2689
|
+
# end method definition
|