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.

Files changed (56) hide show
  1. pyxecm/__init__.py +6 -2
  2. pyxecm/avts.py +1492 -0
  3. pyxecm/coreshare.py +1075 -960
  4. pyxecm/customizer/__init__.py +16 -4
  5. pyxecm/customizer/__main__.py +58 -0
  6. pyxecm/customizer/api/__init__.py +5 -0
  7. pyxecm/customizer/api/__main__.py +6 -0
  8. pyxecm/customizer/api/app.py +914 -0
  9. pyxecm/customizer/api/auth.py +154 -0
  10. pyxecm/customizer/api/metrics.py +92 -0
  11. pyxecm/customizer/api/models.py +13 -0
  12. pyxecm/customizer/api/payload_list.py +865 -0
  13. pyxecm/customizer/api/settings.py +103 -0
  14. pyxecm/customizer/browser_automation.py +332 -139
  15. pyxecm/customizer/customizer.py +1075 -1057
  16. pyxecm/customizer/exceptions.py +35 -0
  17. pyxecm/customizer/guidewire.py +322 -0
  18. pyxecm/customizer/k8s.py +787 -338
  19. pyxecm/customizer/log.py +107 -0
  20. pyxecm/customizer/m365.py +3424 -2270
  21. pyxecm/customizer/nhc.py +1169 -0
  22. pyxecm/customizer/openapi.py +258 -0
  23. pyxecm/customizer/payload.py +18201 -7030
  24. pyxecm/customizer/pht.py +1047 -210
  25. pyxecm/customizer/salesforce.py +836 -727
  26. pyxecm/customizer/sap.py +58 -41
  27. pyxecm/customizer/servicenow.py +851 -383
  28. pyxecm/customizer/settings.py +442 -0
  29. pyxecm/customizer/successfactors.py +408 -346
  30. pyxecm/customizer/translate.py +83 -48
  31. pyxecm/helper/__init__.py +5 -2
  32. pyxecm/helper/assoc.py +98 -38
  33. pyxecm/helper/data.py +2482 -742
  34. pyxecm/helper/logadapter.py +27 -0
  35. pyxecm/helper/web.py +229 -101
  36. pyxecm/helper/xml.py +528 -172
  37. pyxecm/maintenance_page/__init__.py +5 -0
  38. pyxecm/maintenance_page/__main__.py +6 -0
  39. pyxecm/maintenance_page/app.py +51 -0
  40. pyxecm/maintenance_page/settings.py +28 -0
  41. pyxecm/maintenance_page/static/favicon.avif +0 -0
  42. pyxecm/maintenance_page/templates/maintenance.html +165 -0
  43. pyxecm/otac.py +234 -140
  44. pyxecm/otawp.py +2689 -0
  45. pyxecm/otcs.py +12344 -7547
  46. pyxecm/otds.py +3166 -2219
  47. pyxecm/otiv.py +36 -21
  48. pyxecm/otmm.py +1363 -296
  49. pyxecm/otpd.py +231 -127
  50. pyxecm-2.0.0.dist-info/METADATA +145 -0
  51. pyxecm-2.0.0.dist-info/RECORD +54 -0
  52. {pyxecm-1.5.dist-info → pyxecm-2.0.0.dist-info}/WHEEL +1 -1
  53. pyxecm-1.5.dist-info/METADATA +0 -51
  54. pyxecm-1.5.dist-info/RECORD +0 -30
  55. {pyxecm-1.5.dist-info → pyxecm-2.0.0.dist-info/licenses}/LICENSE +0 -0
  56. {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">&amp;(objectclass=busorganizationaluser)(&amp;(!(cn=SYSTEM))(!(cn=anonymous))(!(cn=wcpLicUser)))(|(description=*{user_name}*)(&amp;(!(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