pyxecm 0.0.17__py3-none-any.whl → 0.0.19__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 +10 -10
- pyxecm/assoc.py +139 -0
- pyxecm/m365.py +11 -4
- pyxecm/main.py +28 -22
- pyxecm/otcs.py +79 -73
- pyxecm/otds.py +372 -77
- pyxecm/payload.py +1683 -382
- pyxecm/xml.py +436 -0
- {pyxecm-0.0.17.dist-info → pyxecm-0.0.19.dist-info}/METADATA +4 -5
- pyxecm-0.0.19.dist-info/RECORD +20 -0
- pyxecm/llm.py +0 -451
- pyxecm-0.0.17.dist-info/RECORD +0 -19
- {pyxecm-0.0.17.dist-info → pyxecm-0.0.19.dist-info}/LICENSE +0 -0
- {pyxecm-0.0.17.dist-info → pyxecm-0.0.19.dist-info}/WHEEL +0 -0
- {pyxecm-0.0.17.dist-info → pyxecm-0.0.19.dist-info}/top_level.txt +0 -0
pyxecm/payload.py
CHANGED
|
@@ -30,10 +30,18 @@ __init__ : class initializer
|
|
|
30
30
|
replacePlaceholders: replace placeholder in admin config files
|
|
31
31
|
initPayload: load and initialize the YAML payload
|
|
32
32
|
getPayloadSection: delivers a section of the payload as a list of settings
|
|
33
|
+
getAllGroupNames: construct a list of all group name
|
|
34
|
+
checkStatusFile: check if the payload section has been processed before
|
|
35
|
+
writeStatusFile: Write a status file into the Admin Personal Workspace in Extended ECM
|
|
36
|
+
to indicate that the payload section has been deployed successfully
|
|
37
|
+
determineGroupID: determine the id of a group - either from payload or from OTCS
|
|
38
|
+
determineUserID: determine the id of a user - either from payload or from OTCS
|
|
39
|
+
determineWorkspaceID: determine the nodeID of a workspace - either from payload or from OTCS
|
|
33
40
|
|
|
34
41
|
processPayload: process payload (main method)
|
|
35
42
|
processWebHooks: process list of web hooks
|
|
36
43
|
processPartitions: process the OTDS partitions
|
|
44
|
+
processPartitionLicenses: process the licenses that should be assigned to OTDS partitions (this includes existing partitions)
|
|
37
45
|
processOAuthClients: process the OTDS OAuth clients
|
|
38
46
|
processTrustedSites: process the OTDS trusted sites
|
|
39
47
|
processSystemAttributes: process the OTDS system attributes
|
|
@@ -47,7 +55,6 @@ processTransportPackages: process Extended ECM transport packages
|
|
|
47
55
|
processUserPhotos: process Extended ECM user photos (user profile)
|
|
48
56
|
processUserPhotosM365: process user photos in payload and assign them to Microsoft 365 users.
|
|
49
57
|
processWorkspaceTypes: process Extended ECM workspace types (needs to run after processTransportPackages)
|
|
50
|
-
processWorkspaceTemplateRegistrations: register workspace templates as projects for Extended ECM for Engineering (deprecated)
|
|
51
58
|
processWorkspaces: process Extended ECM workspace instances
|
|
52
59
|
processWorkspaceRelationships: process Extended ECM workspace relationships
|
|
53
60
|
processWorkspaceMembers: process Extended ECM workspace members (users and groups)
|
|
@@ -94,6 +101,8 @@ import logging
|
|
|
94
101
|
import yaml
|
|
95
102
|
import hcl2
|
|
96
103
|
import re
|
|
104
|
+
import time
|
|
105
|
+
import json
|
|
97
106
|
|
|
98
107
|
from urllib.parse import urlparse
|
|
99
108
|
|
|
@@ -101,6 +110,10 @@ from urllib.parse import urlparse
|
|
|
101
110
|
import pyxecm.web as web
|
|
102
111
|
import pyxecm.sap as sap
|
|
103
112
|
|
|
113
|
+
from pyxecm.otcs import OTCS
|
|
114
|
+
from pyxecm.otds import OTDS
|
|
115
|
+
from pyxecm.otac import OTAC
|
|
116
|
+
|
|
104
117
|
logger = logging.getLogger(os.path.basename(__file__))
|
|
105
118
|
|
|
106
119
|
|
|
@@ -109,12 +122,12 @@ class Payload(object):
|
|
|
109
122
|
|
|
110
123
|
# _debug controls whether or not transport processing is
|
|
111
124
|
# stopped if one transport fails:
|
|
112
|
-
_debug = False
|
|
113
|
-
_otcs = None
|
|
114
|
-
_otcs_backend = None
|
|
115
|
-
_otcs_frontend = None
|
|
116
|
-
_otac = None
|
|
117
|
-
_otds = None
|
|
125
|
+
_debug: bool = False
|
|
126
|
+
_otcs: OTCS = None
|
|
127
|
+
_otcs_backend: OTCS = None
|
|
128
|
+
_otcs_frontend: OTCS = None
|
|
129
|
+
_otac: OTAC = None
|
|
130
|
+
_otds: OTDS = None
|
|
118
131
|
_otiv = None
|
|
119
132
|
_k8s = None
|
|
120
133
|
_web = None
|
|
@@ -197,15 +210,18 @@ class Payload(object):
|
|
|
197
210
|
|
|
198
211
|
_placeholder_values = {}
|
|
199
212
|
|
|
213
|
+
_otcs_restart_callback = None
|
|
214
|
+
|
|
200
215
|
def __init__(
|
|
201
216
|
self,
|
|
202
217
|
payload_source: str,
|
|
203
218
|
custom_settings_dir: str,
|
|
204
219
|
k8s_object: object,
|
|
205
|
-
otds_object:
|
|
206
|
-
otac_object:
|
|
207
|
-
otcs_backend_object:
|
|
208
|
-
otcs_frontend_object:
|
|
220
|
+
otds_object: OTDS,
|
|
221
|
+
otac_object: OTAC,
|
|
222
|
+
otcs_backend_object: OTCS,
|
|
223
|
+
otcs_frontend_object: OTCS,
|
|
224
|
+
otcs_restart_callback: callable,
|
|
209
225
|
otiv_object: object,
|
|
210
226
|
m365_object: object,
|
|
211
227
|
placeholder_values: dict = {},
|
|
@@ -216,10 +232,11 @@ class Payload(object):
|
|
|
216
232
|
Args:
|
|
217
233
|
payload_source (string): path or URL to payload source file
|
|
218
234
|
k8s_object (object): Kubernetes object
|
|
219
|
-
otds_object (
|
|
220
|
-
otac_object (
|
|
221
|
-
otcs_backend_object (
|
|
222
|
-
otcs_frontend_object (
|
|
235
|
+
otds_object (OTDS): OTDS object
|
|
236
|
+
otac_object (OTAC): OTAC object
|
|
237
|
+
otcs_backend_object (OTCS): OTCS backend object
|
|
238
|
+
otcs_frontend_object (OTCS): OTCS frontend object
|
|
239
|
+
otcs_restart_callback (callable): function to call if OTCS service needs a restart
|
|
223
240
|
otiv_object (object): OTIV object
|
|
224
241
|
m365_object (object): M365 object to talk to Microsoft Graph API
|
|
225
242
|
placeholder_values (dict): dictionary of placeholder values to be replaced in admin settings
|
|
@@ -238,6 +255,7 @@ class Payload(object):
|
|
|
238
255
|
self._m365 = m365_object
|
|
239
256
|
self._custom_settings_dir = custom_settings_dir
|
|
240
257
|
self._placeholder_values = placeholder_values
|
|
258
|
+
self._otcs_restart_callback = otcs_restart_callback
|
|
241
259
|
|
|
242
260
|
self._http_object = web.HTTP()
|
|
243
261
|
|
|
@@ -357,9 +375,6 @@ class Payload(object):
|
|
|
357
375
|
self._permissions = self.getPayloadSection("permissions")
|
|
358
376
|
self._permissions_post = self.getPayloadSection("permissionsPost")
|
|
359
377
|
self._assignments = self.getPayloadSection("assignments")
|
|
360
|
-
self._workspace_template_registrations = self.getPayloadSection(
|
|
361
|
-
"workspaceTemplateRegistrations",
|
|
362
|
-
)
|
|
363
378
|
self._security_clearances = self.getPayloadSection("securityClearances")
|
|
364
379
|
self._supplemental_markings = self.getPayloadSection("supplementalMarkings")
|
|
365
380
|
self._records_management_settings = self.getPayloadSection(
|
|
@@ -403,8 +418,269 @@ class Payload(object):
|
|
|
403
418
|
# end method definition
|
|
404
419
|
|
|
405
420
|
def getAllGroupNames(self) -> list:
|
|
421
|
+
"""Construct a list of all group name
|
|
422
|
+
|
|
423
|
+
Returns:
|
|
424
|
+
list: list of all group names
|
|
425
|
+
"""
|
|
406
426
|
return [group.get("name") for group in self._groups]
|
|
407
427
|
|
|
428
|
+
# end method definition
|
|
429
|
+
|
|
430
|
+
def checkStatusFile(
|
|
431
|
+
self, payload_section_name: str, payload_specific: bool = True
|
|
432
|
+
) -> bool:
|
|
433
|
+
"""Check if the payload section has been processed before. This is
|
|
434
|
+
done by checking the existance of a text file in the Admin Personal
|
|
435
|
+
workspace in Extended ECM with the name of the payload section.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
payload_section_name (str): name of the payload section. This
|
|
439
|
+
is used to construct the file name
|
|
440
|
+
payload_specific (bool): whether or not the success should be specific for
|
|
441
|
+
each payload file or if success is "global" - like for the deletion
|
|
442
|
+
of the existing M365 teams (which we don't want to execute per
|
|
443
|
+
payload file)
|
|
444
|
+
Returns:
|
|
445
|
+
bool: True if the payload has been processed successfully before, False otherwise
|
|
446
|
+
"""
|
|
447
|
+
|
|
448
|
+
logger.info(
|
|
449
|
+
"Check if payload section -> {} has been processed successfully before...".format(
|
|
450
|
+
payload_section_name
|
|
451
|
+
)
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
response = self._otcs.getNodeByVolumeAndPath(
|
|
455
|
+
142
|
|
456
|
+
) # write to Personal Workspace of Admin
|
|
457
|
+
target_folder_id = self._otcs.getResultValue(response, "id")
|
|
458
|
+
if not target_folder_id:
|
|
459
|
+
target_folder_id = 2004 # use Personal Workspace of Admin as fallback
|
|
460
|
+
|
|
461
|
+
# Some sections are actually not payload specific like teamsM365Cleanup
|
|
462
|
+
# we don't want external payload runs to re-apply this processing:
|
|
463
|
+
if payload_specific:
|
|
464
|
+
file_name = os.path.basename(self._payload_source) # remove directories
|
|
465
|
+
file_name = os.path.splitext(file_name)[0] # remove file suffix
|
|
466
|
+
file_name = "success_" + file_name + "_" + payload_section_name + ".txt"
|
|
467
|
+
else:
|
|
468
|
+
file_name = "success_" + payload_section_name + ".txt"
|
|
469
|
+
|
|
470
|
+
status_document = self._otcs.getNodeByParentAndName(
|
|
471
|
+
parent_id=target_folder_id, name=file_name, show_error=False
|
|
472
|
+
)
|
|
473
|
+
if status_document and status_document["results"]:
|
|
474
|
+
name = self._otcs.getResultValue(status_document, "name")
|
|
475
|
+
if name == file_name:
|
|
476
|
+
logger.info(
|
|
477
|
+
"Payload section -> {} has been processed successfully before. Skipping...".format(
|
|
478
|
+
payload_section_name
|
|
479
|
+
)
|
|
480
|
+
)
|
|
481
|
+
return True
|
|
482
|
+
logger.info(
|
|
483
|
+
"Payload section -> {} has not been processed successfully before. Processing...".format(
|
|
484
|
+
payload_section_name
|
|
485
|
+
)
|
|
486
|
+
)
|
|
487
|
+
return False
|
|
488
|
+
|
|
489
|
+
# end method definition
|
|
490
|
+
|
|
491
|
+
def writeStatusFile(
|
|
492
|
+
self,
|
|
493
|
+
payload_section_name: str,
|
|
494
|
+
payload_section: list,
|
|
495
|
+
payload_specific: bool = True,
|
|
496
|
+
) -> bool:
|
|
497
|
+
"""Write a status file into the Admin Personal Workspace in Extended ECM
|
|
498
|
+
to indicate that the payload section has been deployed successfully.
|
|
499
|
+
This speeds up the customizing process in case the customizer pod
|
|
500
|
+
is restarted.
|
|
501
|
+
|
|
502
|
+
Args:
|
|
503
|
+
payload_section_name (str): name of the payload section
|
|
504
|
+
payload_section (list): payload section content - this is written as JSon into the file
|
|
505
|
+
payload_specific (bool): whether or not the success should be specific for
|
|
506
|
+
each payload file or if success is "global" - like for the deletion
|
|
507
|
+
of the existing M365 teams (which we don't want to execute per
|
|
508
|
+
payload file)
|
|
509
|
+
Returns:
|
|
510
|
+
bool: True if the status file as been upladed to Extended ECM successfully, False otherwise
|
|
511
|
+
"""
|
|
512
|
+
|
|
513
|
+
response = self._otcs.getNodeByVolumeAndPath(
|
|
514
|
+
142
|
|
515
|
+
) # write to Personal Workspace of Admin (with Volume Type ID = 142)
|
|
516
|
+
target_folder_id = self._otcs.getResultValue(response, "id")
|
|
517
|
+
if not target_folder_id:
|
|
518
|
+
target_folder_id = 2004 # use Personal Workspace of Admin as fallback
|
|
519
|
+
|
|
520
|
+
# Some sections are actually not payload specific like teamsM365Cleanup
|
|
521
|
+
# we don't want external payload runs to re-apply this processing:
|
|
522
|
+
if payload_specific:
|
|
523
|
+
file_name = os.path.basename(self._payload_source) # remove directories
|
|
524
|
+
file_name = os.path.splitext(file_name)[0] # remove file suffix
|
|
525
|
+
file_name = "success_" + file_name + "_" + payload_section_name + ".txt"
|
|
526
|
+
else:
|
|
527
|
+
file_name = "success_" + payload_section_name + ".txt"
|
|
528
|
+
full_path = "/tmp/" + file_name
|
|
529
|
+
|
|
530
|
+
with open(full_path, mode="w") as localfile:
|
|
531
|
+
localfile.write(json.dumps(payload_section, indent=2))
|
|
532
|
+
|
|
533
|
+
response = self._otcs.uploadFileToParent(
|
|
534
|
+
file_url=full_path,
|
|
535
|
+
file_name=file_name,
|
|
536
|
+
mime_type="text/plain",
|
|
537
|
+
parent_id=target_folder_id,
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
if response:
|
|
541
|
+
logger.info(
|
|
542
|
+
"Payload section -> {} has been completed successfully!".format(
|
|
543
|
+
payload_section_name
|
|
544
|
+
)
|
|
545
|
+
)
|
|
546
|
+
return True
|
|
547
|
+
|
|
548
|
+
return False
|
|
549
|
+
|
|
550
|
+
# end method definition
|
|
551
|
+
|
|
552
|
+
def determineGroupID(self, group: dict) -> int:
|
|
553
|
+
"""Determine the id of a group - either from payload or from OTCS.
|
|
554
|
+
If the group is found in OTCS write back the ID into the payload.
|
|
555
|
+
|
|
556
|
+
Args:
|
|
557
|
+
group (dict): group payload element
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
int: group ID
|
|
561
|
+
Side Effects:
|
|
562
|
+
the group items are modified by adding an "id" dict element that
|
|
563
|
+
includes the technical ID of the group in Extended ECM
|
|
564
|
+
"""
|
|
565
|
+
|
|
566
|
+
if "id" in group:
|
|
567
|
+
return group["id"]
|
|
568
|
+
|
|
569
|
+
if not "name" in group:
|
|
570
|
+
logger.error("Group needs a name to lookup the ID.")
|
|
571
|
+
return 0
|
|
572
|
+
group_name = group["name"]
|
|
573
|
+
|
|
574
|
+
existing_groups = self._otcs.getGroup(name=group_name)
|
|
575
|
+
if not existing_groups or not existing_groups["data"]:
|
|
576
|
+
logger.info(
|
|
577
|
+
"Cannot find an existing group with name -> {}".format(group_name)
|
|
578
|
+
)
|
|
579
|
+
return 0
|
|
580
|
+
|
|
581
|
+
# Get list of all matching groups:
|
|
582
|
+
existing_groups_list = existing_groups["data"]
|
|
583
|
+
# Find the group with the exact match of the name:
|
|
584
|
+
existing_group = next(
|
|
585
|
+
(item for item in existing_groups_list if item["name"] == group_name),
|
|
586
|
+
None,
|
|
587
|
+
)
|
|
588
|
+
# Have we found an exact match?
|
|
589
|
+
if existing_group:
|
|
590
|
+
group["id"] = existing_group["id"]
|
|
591
|
+
return group["id"]
|
|
592
|
+
else:
|
|
593
|
+
logger.info(
|
|
594
|
+
"Did not find an existing group with name -> {}".format(group_name)
|
|
595
|
+
)
|
|
596
|
+
return 0
|
|
597
|
+
|
|
598
|
+
# end method definition
|
|
599
|
+
|
|
600
|
+
def determineUserID(self, user: dict):
|
|
601
|
+
"""Determine the id of a group - either from payload or from OTCS
|
|
602
|
+
If the user is found in OTCS write back the ID into the payload.
|
|
603
|
+
|
|
604
|
+
Args:
|
|
605
|
+
user (dict): user payload element
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
int: user ID
|
|
609
|
+
Side Effects:
|
|
610
|
+
the user items are modified by adding an "id" dict element that
|
|
611
|
+
includes the technical ID of the user in Extended ECM
|
|
612
|
+
"""
|
|
613
|
+
|
|
614
|
+
if "id" in user:
|
|
615
|
+
return user["id"]
|
|
616
|
+
|
|
617
|
+
if not "name" in user:
|
|
618
|
+
logger.error("User needs a login name to lookup the ID.")
|
|
619
|
+
return 0
|
|
620
|
+
user_name = user["name"]
|
|
621
|
+
|
|
622
|
+
existing_users = self._otcs.getUser(name=user_name)
|
|
623
|
+
if not existing_users or not existing_users["data"]:
|
|
624
|
+
logger.info(
|
|
625
|
+
"Cannot find an existing user with name -> {}".format(user_name)
|
|
626
|
+
)
|
|
627
|
+
return 0
|
|
628
|
+
|
|
629
|
+
# Get list of all matching users:
|
|
630
|
+
existing_users_list = existing_users["data"]
|
|
631
|
+
# Find the group with the exact match of the name:
|
|
632
|
+
existing_user = next(
|
|
633
|
+
(item for item in existing_users_list if item["name"] == user_name),
|
|
634
|
+
None,
|
|
635
|
+
)
|
|
636
|
+
# Have we found an exact match?
|
|
637
|
+
if existing_user:
|
|
638
|
+
user["id"] = existing_user["id"]
|
|
639
|
+
return user["id"]
|
|
640
|
+
else:
|
|
641
|
+
logger.info(
|
|
642
|
+
"Did not find an existing user with name -> {}".format(user_name)
|
|
643
|
+
)
|
|
644
|
+
return 0
|
|
645
|
+
|
|
646
|
+
# end method definition
|
|
647
|
+
|
|
648
|
+
def determineWorkspaceID(self, workspace: dict):
|
|
649
|
+
"""Determine the nodeID of a workspace - either from payload or from OTCS"""
|
|
650
|
+
|
|
651
|
+
"""Determine the id of a group - either from payload or from OTCS
|
|
652
|
+
If the user is found in OTCS write back the ID into the payload.
|
|
653
|
+
|
|
654
|
+
Args:
|
|
655
|
+
workspace (dict): workspace payload element
|
|
656
|
+
|
|
657
|
+
Returns:
|
|
658
|
+
int: workspace Node ID
|
|
659
|
+
Side Effects:
|
|
660
|
+
the workspace items are modified by adding an "nodeId" dict element that
|
|
661
|
+
includes the node ID of the workspace in Extended ECM
|
|
662
|
+
"""
|
|
663
|
+
|
|
664
|
+
if "nodeId" in workspace:
|
|
665
|
+
return workspace["nodeId"]
|
|
666
|
+
|
|
667
|
+
response = self._otcs.getWorkspaceByTypeAndName(
|
|
668
|
+
workspace["type_name"], workspace["name"]
|
|
669
|
+
)
|
|
670
|
+
workspace_id = self._otcs.getResultValue(response, "id")
|
|
671
|
+
if workspace_id:
|
|
672
|
+
workspace["nodeId"] = workspace_id
|
|
673
|
+
return workspace_id
|
|
674
|
+
else:
|
|
675
|
+
logger.warning(
|
|
676
|
+
"Workspace of type -> {} and name -> {} does not exist.".format(
|
|
677
|
+
workspace["type_name"], workspace["name"]
|
|
678
|
+
)
|
|
679
|
+
)
|
|
680
|
+
return 0
|
|
681
|
+
|
|
682
|
+
# end method definition
|
|
683
|
+
|
|
408
684
|
def processPayload(self):
|
|
409
685
|
"""Main method to process a payload file.
|
|
410
686
|
|
|
@@ -430,12 +706,16 @@ class Payload(object):
|
|
|
430
706
|
logger.info(
|
|
431
707
|
"========== Process Web Hooks (post) ===================="
|
|
432
708
|
)
|
|
433
|
-
self.processWebHooks(self._webhooks_post)
|
|
709
|
+
self.processWebHooks(self._webhooks_post, "webHooksPost")
|
|
434
710
|
case "partitions":
|
|
435
711
|
logger.info(
|
|
436
712
|
"========== Process OTDS Partitions ====================="
|
|
437
713
|
)
|
|
438
714
|
self.processPartitions()
|
|
715
|
+
logger.info(
|
|
716
|
+
"========== Assign OTCS Licenses to Partitions =========="
|
|
717
|
+
)
|
|
718
|
+
self.processPartitionLicenses()
|
|
439
719
|
case "oauthClients":
|
|
440
720
|
logger.info(
|
|
441
721
|
"========== Process OTDS OAuth Clients =================="
|
|
@@ -456,6 +736,11 @@ class Payload(object):
|
|
|
456
736
|
"========== Process OTCS Groups ========================="
|
|
457
737
|
)
|
|
458
738
|
self.processGroups()
|
|
739
|
+
# Add all groups with ID the a lookup dict for placeholder replacements
|
|
740
|
+
# in adminSetting. This also updates the payload with group IDs from OTCS
|
|
741
|
+
# if the group already exists in Extended ECM. This is important especially
|
|
742
|
+
# if the customizer pod is restarted / run multiple times:
|
|
743
|
+
self.processGroupPlaceholders()
|
|
459
744
|
if self._m365:
|
|
460
745
|
logger.info(
|
|
461
746
|
"========== Cleanup existing MS Teams ==================="
|
|
@@ -470,6 +755,11 @@ class Payload(object):
|
|
|
470
755
|
"========== Process OTCS Users =========================="
|
|
471
756
|
)
|
|
472
757
|
self.processUsers()
|
|
758
|
+
# Add all users with ID the a lookup dict for placeholder replacements
|
|
759
|
+
# in adminSetting. This also updates the payload with user IDs from OTCS
|
|
760
|
+
# if the user already exists in Extended ECM. This is important especially
|
|
761
|
+
# if the cutomizer pod is restarted / run multiple times:
|
|
762
|
+
self.processUserPlaceholders()
|
|
473
763
|
logger.info(
|
|
474
764
|
"========== Assign OTCS Licenses to Users ==============="
|
|
475
765
|
)
|
|
@@ -505,14 +795,16 @@ class Payload(object):
|
|
|
505
795
|
self.processTeamsM365()
|
|
506
796
|
case "adminSettings":
|
|
507
797
|
logger.info(
|
|
508
|
-
"========== Process
|
|
798
|
+
"========== Process Administration Settings (1) ========="
|
|
509
799
|
)
|
|
510
800
|
self.processAdminSettings(self._admin_settings)
|
|
511
801
|
case "adminSettingsPost":
|
|
512
802
|
logger.info(
|
|
513
|
-
"========== Process
|
|
803
|
+
"========== Process Administration Settings (2) ========="
|
|
804
|
+
)
|
|
805
|
+
self.processAdminSettings(
|
|
806
|
+
self._admin_settings_post, "adminSettingsPost"
|
|
514
807
|
)
|
|
515
|
-
self.processAdminSettings(self._admin_settings_post)
|
|
516
808
|
case "execPodCommands":
|
|
517
809
|
logger.info(
|
|
518
810
|
"========== Process Pod Commands ========================"
|
|
@@ -522,11 +814,15 @@ class Payload(object):
|
|
|
522
814
|
logger.info(
|
|
523
815
|
"========== Process CS Apps (backend) ==================="
|
|
524
816
|
)
|
|
525
|
-
self.processCSApplications(
|
|
817
|
+
self.processCSApplications(
|
|
818
|
+
self._otcs_backend, section_name="csApplicationsBackend"
|
|
819
|
+
)
|
|
526
820
|
logger.info(
|
|
527
821
|
"========== Process CS Apps (frontend) =================="
|
|
528
822
|
)
|
|
529
|
-
self.processCSApplications(
|
|
823
|
+
self.processCSApplications(
|
|
824
|
+
self._otcs_frontend, section_name="csApplicationsFrontend"
|
|
825
|
+
)
|
|
530
826
|
case "externalSystems":
|
|
531
827
|
logger.info(
|
|
532
828
|
"========== Process External System Connections ========="
|
|
@@ -547,7 +843,9 @@ class Payload(object):
|
|
|
547
843
|
logger.info(
|
|
548
844
|
"========== Process Content Transport Packages =========="
|
|
549
845
|
)
|
|
550
|
-
self.processTransportPackages(
|
|
846
|
+
self.processTransportPackages(
|
|
847
|
+
self._content_transport_packages, "contentTransportPackages"
|
|
848
|
+
)
|
|
551
849
|
case "transportPackagesPost":
|
|
552
850
|
# if self._m365:
|
|
553
851
|
# logger.info(
|
|
@@ -557,12 +855,9 @@ class Payload(object):
|
|
|
557
855
|
logger.info(
|
|
558
856
|
"========== Process Transport Packages (post) ==========="
|
|
559
857
|
)
|
|
560
|
-
self.processTransportPackages(
|
|
561
|
-
|
|
562
|
-
logger.info(
|
|
563
|
-
"========== Register Workspace Templates ================"
|
|
858
|
+
self.processTransportPackages(
|
|
859
|
+
self._transport_packages_post, "transportPackagesPost"
|
|
564
860
|
)
|
|
565
|
-
self.processWorkspaceTemplateRegistrations()
|
|
566
861
|
case "workspaces":
|
|
567
862
|
logger.info(
|
|
568
863
|
"========== Process Workspaces =========================="
|
|
@@ -586,7 +881,8 @@ class Payload(object):
|
|
|
586
881
|
(
|
|
587
882
|
item
|
|
588
883
|
for item in self._external_systems
|
|
589
|
-
if item
|
|
884
|
+
if item.get("external_system_type")
|
|
885
|
+
and item["external_system_type"] == "SAP"
|
|
590
886
|
),
|
|
591
887
|
None,
|
|
592
888
|
)
|
|
@@ -615,7 +911,7 @@ class Payload(object):
|
|
|
615
911
|
logger.info(
|
|
616
912
|
"========== Process Web Reports (post) =================="
|
|
617
913
|
)
|
|
618
|
-
self.processWebReports(self._web_reports_post)
|
|
914
|
+
self.processWebReports(self._web_reports_post, "webReportsPost")
|
|
619
915
|
case "additionalGroupMemberships":
|
|
620
916
|
logger.info(
|
|
621
917
|
"========== Process additional group members for OTDS ==="
|
|
@@ -640,7 +936,7 @@ class Payload(object):
|
|
|
640
936
|
logger.info(
|
|
641
937
|
"========== Process Items (post) ========================"
|
|
642
938
|
)
|
|
643
|
-
self.processItems(self._items_post)
|
|
939
|
+
self.processItems(self._items_post, "itemsPost")
|
|
644
940
|
case "permissions":
|
|
645
941
|
logger.info(
|
|
646
942
|
"========== Process Permissions ========================="
|
|
@@ -687,6 +983,23 @@ class Payload(object):
|
|
|
687
983
|
payload_section["name"]
|
|
688
984
|
)
|
|
689
985
|
)
|
|
986
|
+
payload_section_restart = payload_section.get("restart", False)
|
|
987
|
+
if payload_section_restart:
|
|
988
|
+
logger.info(
|
|
989
|
+
"Payload section -> {} requests a restart of OTCS services...".format(
|
|
990
|
+
payload_section["name"]
|
|
991
|
+
)
|
|
992
|
+
)
|
|
993
|
+
# Restart OTCS frontend and backend pods:
|
|
994
|
+
self._otcs_restart_callback(self._otcs_backend, self._k8s)
|
|
995
|
+
# give some additional time to make sure service is responsive
|
|
996
|
+
time.sleep(30)
|
|
997
|
+
else:
|
|
998
|
+
logger.info(
|
|
999
|
+
"Payload section -> {} does not require a restart of OTCS services".format(
|
|
1000
|
+
payload_section["name"]
|
|
1001
|
+
)
|
|
1002
|
+
)
|
|
690
1003
|
|
|
691
1004
|
if self._users:
|
|
692
1005
|
logger.info("========== Process User Profile Photos =================")
|
|
@@ -701,22 +1014,35 @@ class Payload(object):
|
|
|
701
1014
|
|
|
702
1015
|
# end method definition
|
|
703
1016
|
|
|
704
|
-
def processWebHooks(self, webhooks: list):
|
|
1017
|
+
def processWebHooks(self, webhooks: list, section_name: str = "webHooks") -> bool:
|
|
705
1018
|
"""Process Web Hooks in payload and do HTTP requests.
|
|
706
1019
|
|
|
707
1020
|
Args:
|
|
708
1021
|
None
|
|
709
1022
|
Returns:
|
|
710
|
-
|
|
1023
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
711
1024
|
"""
|
|
712
1025
|
|
|
713
1026
|
if not webhooks:
|
|
714
|
-
|
|
1027
|
+
logger.info(
|
|
1028
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
1029
|
+
)
|
|
1030
|
+
return True
|
|
1031
|
+
|
|
1032
|
+
# if this payload section has been processed successfully before we can return True
|
|
1033
|
+
# and skip processing once more
|
|
1034
|
+
|
|
1035
|
+
# WE LET THIS RUN EACH TIME!
|
|
1036
|
+
# if self.checkStatusFile(section_name):
|
|
1037
|
+
# return True
|
|
1038
|
+
|
|
1039
|
+
success: bool = True
|
|
715
1040
|
|
|
716
1041
|
for webhook in webhooks:
|
|
717
1042
|
url = webhook.get("url")
|
|
718
1043
|
if not url:
|
|
719
1044
|
logger.info("Web Hook does not have a url - skipping...")
|
|
1045
|
+
success = False
|
|
720
1046
|
continue
|
|
721
1047
|
|
|
722
1048
|
# Check if element has been disabled in payload (enabled = false).
|
|
@@ -744,21 +1070,40 @@ class Payload(object):
|
|
|
744
1070
|
|
|
745
1071
|
self._http_object.httpRequest(url, method, payload, headers)
|
|
746
1072
|
|
|
1073
|
+
# if success:
|
|
1074
|
+
# self.writeStatusFile(section_name, webhooks)
|
|
1075
|
+
|
|
1076
|
+
return success
|
|
1077
|
+
|
|
747
1078
|
# end method definition
|
|
748
1079
|
|
|
749
|
-
def processPartitions(self):
|
|
1080
|
+
def processPartitions(self, section_name: str = "partitions") -> bool:
|
|
750
1081
|
"""Process OTDS partitions in payload and create them in OTDS.
|
|
751
1082
|
|
|
752
1083
|
Args:
|
|
753
1084
|
None
|
|
754
1085
|
Returns:
|
|
755
|
-
|
|
1086
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
756
1087
|
"""
|
|
757
1088
|
|
|
1089
|
+
if not self._partitions:
|
|
1090
|
+
logger.info(
|
|
1091
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
1092
|
+
)
|
|
1093
|
+
return True
|
|
1094
|
+
|
|
1095
|
+
# if this payload section has been processed successfully before we can return True
|
|
1096
|
+
# and skip processing once more
|
|
1097
|
+
if self.checkStatusFile(section_name):
|
|
1098
|
+
return True
|
|
1099
|
+
|
|
1100
|
+
success: bool = True
|
|
1101
|
+
|
|
758
1102
|
for partition in self._partitions:
|
|
759
1103
|
partition_name = partition.get("name")
|
|
760
1104
|
if not partition_name:
|
|
761
1105
|
logger.error("Partition does not have a name - skipping...")
|
|
1106
|
+
success = False
|
|
762
1107
|
continue
|
|
763
1108
|
|
|
764
1109
|
# Check if element has been disabled in payload (enabled = false).
|
|
@@ -800,6 +1145,7 @@ class Payload(object):
|
|
|
800
1145
|
logger.error(
|
|
801
1146
|
"Failed to add OTDS partition -> {}".format(partition_name)
|
|
802
1147
|
)
|
|
1148
|
+
success = False
|
|
803
1149
|
continue
|
|
804
1150
|
|
|
805
1151
|
access_role = partition.get("access_role")
|
|
@@ -819,6 +1165,7 @@ class Payload(object):
|
|
|
819
1165
|
partition_name, access_role
|
|
820
1166
|
)
|
|
821
1167
|
)
|
|
1168
|
+
success = False
|
|
822
1169
|
continue
|
|
823
1170
|
|
|
824
1171
|
# Partions may have an optional list of licenses in
|
|
@@ -832,9 +1179,10 @@ class Payload(object):
|
|
|
832
1179
|
logger.error(
|
|
833
1180
|
"Cannot find OTCS resource -> {}".format(otcs_resource_name)
|
|
834
1181
|
)
|
|
1182
|
+
success = False
|
|
835
1183
|
continue
|
|
836
1184
|
otcs_resource_id = otcs_resource["resourceID"]
|
|
837
|
-
license_name = "
|
|
1185
|
+
license_name = "EXTENDED_ECM"
|
|
838
1186
|
for license_feature in partition_specific_licenses:
|
|
839
1187
|
assigned_license = self._otds.assignPartitionToLicense(
|
|
840
1188
|
partition_name,
|
|
@@ -849,6 +1197,7 @@ class Payload(object):
|
|
|
849
1197
|
partition_name, license_feature, license_name
|
|
850
1198
|
)
|
|
851
1199
|
)
|
|
1200
|
+
success = False
|
|
852
1201
|
else:
|
|
853
1202
|
logger.info(
|
|
854
1203
|
"Successfully assigned partition -> {} to license feature -> {} of license -> {}".format(
|
|
@@ -856,21 +1205,144 @@ class Payload(object):
|
|
|
856
1205
|
)
|
|
857
1206
|
)
|
|
858
1207
|
|
|
1208
|
+
if success:
|
|
1209
|
+
self.writeStatusFile(section_name, self._partitions)
|
|
1210
|
+
|
|
1211
|
+
return success
|
|
1212
|
+
|
|
859
1213
|
# end method definition
|
|
860
1214
|
|
|
861
|
-
def
|
|
862
|
-
"""Process
|
|
1215
|
+
def processPartitionLicenses(self, section_name: str = "partitionLicenses") -> bool:
|
|
1216
|
+
"""Process the licenses that should be assigned to OTDS partitions
|
|
1217
|
+
(this includes existing partitions).
|
|
863
1218
|
|
|
864
1219
|
Args:
|
|
865
1220
|
None
|
|
866
1221
|
Returns:
|
|
1222
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
1223
|
+
"""
|
|
1224
|
+
|
|
1225
|
+
if not self._partitions:
|
|
1226
|
+
logger.info(
|
|
1227
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
1228
|
+
)
|
|
1229
|
+
return True
|
|
1230
|
+
|
|
1231
|
+
# if this payload section has been processed successfully before we can return True
|
|
1232
|
+
# and skip processing once more
|
|
1233
|
+
if self.checkStatusFile(section_name):
|
|
1234
|
+
return True
|
|
1235
|
+
|
|
1236
|
+
success: bool = True
|
|
1237
|
+
|
|
1238
|
+
for partition in self._partitions:
|
|
1239
|
+
partition_name = partition.get("name")
|
|
1240
|
+
if not partition_name:
|
|
1241
|
+
logger.error("Partition does not have a name - skipping...")
|
|
1242
|
+
success = False
|
|
1243
|
+
continue
|
|
1244
|
+
|
|
1245
|
+
# Check if element has been disabled in payload (enabled = false).
|
|
1246
|
+
# In this case we skip the element:
|
|
1247
|
+
if "enabled" in partition and not partition["enabled"]:
|
|
1248
|
+
logger.info(
|
|
1249
|
+
"Payload for Partition -> {} is disabled. Skipping...".format(
|
|
1250
|
+
partition_name
|
|
1251
|
+
)
|
|
1252
|
+
)
|
|
1253
|
+
continue
|
|
1254
|
+
|
|
1255
|
+
response = self._otds.getPartition(partition_name, show_error=True)
|
|
1256
|
+
if not response:
|
|
1257
|
+
logger.error(
|
|
1258
|
+
"Partition -> {} does not exist. Skipping...".format(partition_name)
|
|
1259
|
+
)
|
|
1260
|
+
success = False
|
|
1261
|
+
continue
|
|
1262
|
+
|
|
1263
|
+
# Partions may have an optional list of licenses in
|
|
1264
|
+
# the payload. Assign the partition to all these licenses:
|
|
1265
|
+
partition_specific_licenses = partition.get("licenses")
|
|
1266
|
+
if partition_specific_licenses:
|
|
1267
|
+
# We assume these licenses are Extended ECM licenses!
|
|
1268
|
+
otcs_resource_name = self._otcs.config()["resource"]
|
|
1269
|
+
otcs_resource = self._otds.getResource(otcs_resource_name)
|
|
1270
|
+
if not otcs_resource:
|
|
1271
|
+
logger.error(
|
|
1272
|
+
"Cannot find OTCS resource -> {}".format(otcs_resource_name)
|
|
1273
|
+
)
|
|
1274
|
+
success = False
|
|
1275
|
+
continue
|
|
1276
|
+
otcs_resource_id = otcs_resource["resourceID"]
|
|
1277
|
+
license_name = "EXTENDED_ECM"
|
|
1278
|
+
for license_feature in partition_specific_licenses:
|
|
1279
|
+
if self._otds.isPartitionLicensed(
|
|
1280
|
+
partition_name=partition_name,
|
|
1281
|
+
resource_id=otcs_resource_id,
|
|
1282
|
+
license_feature=license_feature,
|
|
1283
|
+
license_name=license_name,
|
|
1284
|
+
):
|
|
1285
|
+
logger.info(
|
|
1286
|
+
"Partition -> {} is already licensed for -> {} ({})".format(
|
|
1287
|
+
partition_name, license_name, license_feature
|
|
1288
|
+
)
|
|
1289
|
+
)
|
|
1290
|
+
continue
|
|
1291
|
+
assigned_license = self._otds.assignPartitionToLicense(
|
|
1292
|
+
partition_name,
|
|
1293
|
+
otcs_resource_id,
|
|
1294
|
+
license_feature,
|
|
1295
|
+
license_name,
|
|
1296
|
+
)
|
|
1297
|
+
|
|
1298
|
+
if not assigned_license:
|
|
1299
|
+
logger.error(
|
|
1300
|
+
"Failed to assign partition -> {} to license feature -> {} of license -> {}!".format(
|
|
1301
|
+
partition_name, license_feature, license_name
|
|
1302
|
+
)
|
|
1303
|
+
)
|
|
1304
|
+
success = False
|
|
1305
|
+
else:
|
|
1306
|
+
logger.info(
|
|
1307
|
+
"Successfully assigned partition -> {} to license feature -> {} of license -> {}".format(
|
|
1308
|
+
partition_name, license_feature, license_name
|
|
1309
|
+
)
|
|
1310
|
+
)
|
|
1311
|
+
|
|
1312
|
+
if success:
|
|
1313
|
+
self.writeStatusFile(section_name, self._partitions)
|
|
1314
|
+
|
|
1315
|
+
return success
|
|
1316
|
+
|
|
1317
|
+
# end method definition
|
|
1318
|
+
|
|
1319
|
+
def processOAuthClients(self, section_name: str = "oauthClients") -> bool:
|
|
1320
|
+
"""Process OTDS OAuth clients in payload and create them in OTDS.
|
|
1321
|
+
|
|
1322
|
+
Args:
|
|
867
1323
|
None
|
|
1324
|
+
Returns:
|
|
1325
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
868
1326
|
"""
|
|
869
1327
|
|
|
1328
|
+
if not self._oauth_clients:
|
|
1329
|
+
logger.info(
|
|
1330
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
1331
|
+
)
|
|
1332
|
+
return True
|
|
1333
|
+
|
|
1334
|
+
# if this payload section has been processed successfully before we can return True
|
|
1335
|
+
# and skip processing once more
|
|
1336
|
+
if self.checkStatusFile(section_name):
|
|
1337
|
+
return True
|
|
1338
|
+
|
|
1339
|
+
success: bool = True
|
|
1340
|
+
|
|
870
1341
|
for oauth_client in self._oauth_clients:
|
|
871
1342
|
client_name = oauth_client.get("name")
|
|
872
1343
|
if not client_name:
|
|
873
1344
|
logger.error("OAuth client does not have a name - skipping...")
|
|
1345
|
+
success = False
|
|
874
1346
|
continue
|
|
875
1347
|
|
|
876
1348
|
# Check if element has been disabled in payload (enabled = false).
|
|
@@ -929,6 +1401,7 @@ class Payload(object):
|
|
|
929
1401
|
logger.error(
|
|
930
1402
|
"Failed to add OTDS OAuth client -> {}".format(client_name)
|
|
931
1403
|
)
|
|
1404
|
+
success = False
|
|
932
1405
|
continue
|
|
933
1406
|
|
|
934
1407
|
client_secret = response.get("secret")
|
|
@@ -943,83 +1416,241 @@ class Payload(object):
|
|
|
943
1416
|
client_name, {"description": client_description}
|
|
944
1417
|
)
|
|
945
1418
|
|
|
1419
|
+
if success:
|
|
1420
|
+
self.writeStatusFile(section_name, self._oauth_clients)
|
|
1421
|
+
|
|
1422
|
+
return success
|
|
1423
|
+
|
|
946
1424
|
# self._otds.addOauthClientsToAccessRole()
|
|
947
1425
|
|
|
948
1426
|
# end method definition
|
|
949
1427
|
|
|
950
|
-
def processTrustedSites(self):
|
|
1428
|
+
def processTrustedSites(self, section_name: str = "trustedSites") -> bool:
|
|
951
1429
|
"""Process OTDS trusted sites in payload and create them in OTDS.
|
|
952
1430
|
|
|
953
1431
|
Args:
|
|
954
1432
|
None
|
|
955
1433
|
Returns:
|
|
956
|
-
|
|
1434
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
957
1435
|
"""
|
|
958
1436
|
|
|
1437
|
+
if not self._trusted_sites:
|
|
1438
|
+
logger.info(
|
|
1439
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
1440
|
+
)
|
|
1441
|
+
return True
|
|
1442
|
+
|
|
1443
|
+
# if this payload section has been processed successfully before we can return True
|
|
1444
|
+
# and skip processing once more
|
|
1445
|
+
if self.checkStatusFile(section_name):
|
|
1446
|
+
return True
|
|
1447
|
+
|
|
1448
|
+
success: bool = True
|
|
1449
|
+
|
|
959
1450
|
for trusted_site in self._trusted_sites:
|
|
960
|
-
self._otds.addTrustedSite(trusted_site)
|
|
961
|
-
|
|
1451
|
+
response = self._otds.addTrustedSite(trusted_site)
|
|
1452
|
+
if response:
|
|
1453
|
+
logger.info("Added OTDS trusted site -> {}".format(trusted_site))
|
|
1454
|
+
else:
|
|
1455
|
+
logger.error("Failed to add trusted site -> {}".format(trusted_site))
|
|
1456
|
+
success = False
|
|
1457
|
+
|
|
1458
|
+
if success:
|
|
1459
|
+
self.writeStatusFile(section_name, self._trusted_sites)
|
|
1460
|
+
|
|
1461
|
+
return success
|
|
962
1462
|
|
|
963
1463
|
# end method definition
|
|
964
1464
|
|
|
965
|
-
def processSystemAttributes(self):
|
|
1465
|
+
def processSystemAttributes(self, section_name: str = "systemAttributes") -> bool:
|
|
966
1466
|
"""Process OTDS system attributes in payload and create them in OTDS.
|
|
967
1467
|
|
|
968
1468
|
Args:
|
|
969
1469
|
None
|
|
970
1470
|
Returns:
|
|
971
|
-
|
|
1471
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
972
1472
|
"""
|
|
973
1473
|
|
|
1474
|
+
if not self._system_attributes:
|
|
1475
|
+
logger.info(
|
|
1476
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
1477
|
+
)
|
|
1478
|
+
return True
|
|
1479
|
+
|
|
1480
|
+
# if this payload section has been processed successfully before we can return True
|
|
1481
|
+
# and skip processing once more
|
|
1482
|
+
if self.checkStatusFile(section_name):
|
|
1483
|
+
return True
|
|
1484
|
+
|
|
1485
|
+
success: bool = True
|
|
1486
|
+
|
|
974
1487
|
for system_attribute in self._system_attributes:
|
|
975
1488
|
# Check if there's a matching formal parameter defined on the Web Report node:
|
|
976
1489
|
if not system_attribute.get("name"):
|
|
977
1490
|
logger.error("OTDS System Attribute needs a name. Skipping...")
|
|
1491
|
+
success = False
|
|
978
1492
|
continue
|
|
979
1493
|
attribute_name = system_attribute["name"]
|
|
980
1494
|
|
|
981
|
-
if "enabled" in system_attribute and not system_attribute["enabled"]:
|
|
1495
|
+
if "enabled" in system_attribute and not system_attribute["enabled"]:
|
|
1496
|
+
logger.info(
|
|
1497
|
+
"Payload for OTDS System Attribute -> {} is disabled. Skipping...".format(
|
|
1498
|
+
attribute_name
|
|
1499
|
+
)
|
|
1500
|
+
)
|
|
1501
|
+
continue
|
|
1502
|
+
|
|
1503
|
+
if not system_attribute.get("value"):
|
|
1504
|
+
logger.error("OTDS System Attribute needs a value. Skipping...")
|
|
1505
|
+
continue
|
|
1506
|
+
|
|
1507
|
+
attribute_value = system_attribute["value"]
|
|
1508
|
+
attribute_description = system_attribute.get("description")
|
|
1509
|
+
response = self._otds.addSystemAttribute(
|
|
1510
|
+
attribute_name, attribute_value, attribute_description
|
|
1511
|
+
)
|
|
1512
|
+
if response:
|
|
1513
|
+
logger.info(
|
|
1514
|
+
"Added OTDS system attribute -> {} with value -> {}".format(
|
|
1515
|
+
attribute_name, attribute_value
|
|
1516
|
+
)
|
|
1517
|
+
)
|
|
1518
|
+
else:
|
|
1519
|
+
logger.error(
|
|
1520
|
+
"Failed to add OTDS system attribute -> {} with value -> {}".format(
|
|
1521
|
+
attribute_name, attribute_value
|
|
1522
|
+
)
|
|
1523
|
+
)
|
|
1524
|
+
success = False
|
|
1525
|
+
|
|
1526
|
+
if success:
|
|
1527
|
+
self.writeStatusFile(section_name, self._system_attributes)
|
|
1528
|
+
|
|
1529
|
+
return success
|
|
1530
|
+
|
|
1531
|
+
# end method definition
|
|
1532
|
+
|
|
1533
|
+
def processGroupPlaceholders(self):
|
|
1534
|
+
"""For some adminSettings we may need to replace a placeholder (sourrounded by %%...%%)
|
|
1535
|
+
with the actual ID of the Extended ECM group. For this we prepare a lookup dict.
|
|
1536
|
+
The dict self._placeholder_values already includes lookups for the OTCS and OTAWP
|
|
1537
|
+
OTDS resource IDs (see main.py)
|
|
1538
|
+
"""
|
|
1539
|
+
|
|
1540
|
+
for group in self._groups:
|
|
1541
|
+
if not "name" in group:
|
|
1542
|
+
logger.error(
|
|
1543
|
+
"Group needs a name for placeholder definition. Skipping..."
|
|
1544
|
+
)
|
|
1545
|
+
continue
|
|
1546
|
+
group_name = group["name"]
|
|
1547
|
+
# Check if group has been disabled in payload (enabled = false).
|
|
1548
|
+
# In this case we skip the element:
|
|
1549
|
+
if "enabled" in group and not group["enabled"]:
|
|
1550
|
+
logger.info(
|
|
1551
|
+
"Payload for Group -> {} is disabled. Skipping...".format(
|
|
1552
|
+
group_name
|
|
1553
|
+
)
|
|
1554
|
+
)
|
|
1555
|
+
continue
|
|
1556
|
+
|
|
1557
|
+
# Now we determine the ID. Either it is in the payload section from
|
|
1558
|
+
# the current customizer run or we try to look it up in the system.
|
|
1559
|
+
# The latter case may happen if the custiomuer pod got restarted.
|
|
1560
|
+
group_id = self.determineGroupID(group)
|
|
1561
|
+
if not group_id:
|
|
1562
|
+
logger.warning(
|
|
1563
|
+
"Group needs an ID for placeholder definition. Skipping..."
|
|
1564
|
+
)
|
|
1565
|
+
continue
|
|
1566
|
+
|
|
1567
|
+
# Add Group with its ID to the dict self._placeholder_values:
|
|
1568
|
+
self._placeholder_values[
|
|
1569
|
+
"OTCS_GROUP_ID_{}".format(
|
|
1570
|
+
group_name.upper().replace(" & ", "_").replace(" ", "_")
|
|
1571
|
+
)
|
|
1572
|
+
] = str(group_id)
|
|
1573
|
+
|
|
1574
|
+
logger.debug(
|
|
1575
|
+
"Placeholder values after group processing = {}".format(
|
|
1576
|
+
self._placeholder_values
|
|
1577
|
+
)
|
|
1578
|
+
)
|
|
1579
|
+
|
|
1580
|
+
def processUserPlaceholders(self):
|
|
1581
|
+
"""For some adminSettings we may need to replace a placeholder (sourrounded by %%...%%)
|
|
1582
|
+
with the actual ID of the Extended ECM user. For this we prepare a lookup dict.
|
|
1583
|
+
The dict self._placeholder_values already includes lookups for the OTCS and OTAWP
|
|
1584
|
+
OTDS resource IDs (see main.py)
|
|
1585
|
+
"""
|
|
1586
|
+
|
|
1587
|
+
for user in self._users:
|
|
1588
|
+
if not "name" in user:
|
|
1589
|
+
logger.error(
|
|
1590
|
+
"User needs a name for placeholder definition. Skipping..."
|
|
1591
|
+
)
|
|
1592
|
+
continue
|
|
1593
|
+
user_name = user["name"]
|
|
1594
|
+
# Check if group has been disabled in payload (enabled = false).
|
|
1595
|
+
# In this case we skip the element:
|
|
1596
|
+
if "enabled" in user and not user["enabled"]:
|
|
982
1597
|
logger.info(
|
|
983
|
-
"Payload for
|
|
984
|
-
attribute_name
|
|
985
|
-
)
|
|
1598
|
+
"Payload for User -> {} is disabled. Skipping...".format(user_name)
|
|
986
1599
|
)
|
|
987
1600
|
continue
|
|
988
1601
|
|
|
989
|
-
|
|
990
|
-
|
|
1602
|
+
# Now we determine the ID. Either it is in the payload section from
|
|
1603
|
+
# the current customizer run or we try to look it up in the system.
|
|
1604
|
+
# The latter case may happen if the custiomuer pod got restarted.
|
|
1605
|
+
user_id = self.determineUserID(user)
|
|
1606
|
+
if not user_id:
|
|
1607
|
+
logger.warning(
|
|
1608
|
+
"User needs an ID for placeholder definition. Skipping..."
|
|
1609
|
+
)
|
|
991
1610
|
continue
|
|
992
1611
|
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
attribute_name, attribute_value, attribute_description
|
|
997
|
-
)
|
|
998
|
-
logger.info(
|
|
999
|
-
"Added OTDS system attribute -> {} with value -> {}".format(
|
|
1000
|
-
attribute_name, attribute_value
|
|
1001
|
-
)
|
|
1612
|
+
# Add Group with its ID to the dict self._placeholder_values:
|
|
1613
|
+
self._placeholder_values["OTCS_USER_ID_{}".format(user_name.upper())] = str(
|
|
1614
|
+
user_id
|
|
1002
1615
|
)
|
|
1003
1616
|
|
|
1004
|
-
|
|
1617
|
+
logger.debug(
|
|
1618
|
+
"Placeholder values after user processing = {}".format(
|
|
1619
|
+
self._placeholder_values
|
|
1620
|
+
)
|
|
1621
|
+
)
|
|
1005
1622
|
|
|
1006
|
-
def processGroups(self):
|
|
1623
|
+
def processGroups(self, section_name: str = "groups") -> bool:
|
|
1007
1624
|
"""Process groups in payload and create them in Extended ECM.
|
|
1008
1625
|
|
|
1009
1626
|
Args:
|
|
1010
1627
|
None
|
|
1011
1628
|
Returns:
|
|
1012
|
-
|
|
1629
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
1013
1630
|
Side Effects:
|
|
1014
1631
|
the group items are modified by adding an "id" dict element that
|
|
1015
1632
|
includes the technical ID of the group in Extended ECM
|
|
1016
1633
|
"""
|
|
1017
1634
|
|
|
1635
|
+
if not self._groups:
|
|
1636
|
+
logger.info(
|
|
1637
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
1638
|
+
)
|
|
1639
|
+
return True
|
|
1640
|
+
|
|
1641
|
+
# if this payload section has been processed successfully before we can return True
|
|
1642
|
+
# and skip processing once more
|
|
1643
|
+
if self.checkStatusFile(section_name):
|
|
1644
|
+
return True
|
|
1645
|
+
|
|
1646
|
+
success: bool = True
|
|
1647
|
+
|
|
1018
1648
|
# First run through groups: create all groups in payload
|
|
1019
1649
|
# and store the IDs of the created groups:
|
|
1020
1650
|
for group in self._groups:
|
|
1021
1651
|
if not "name" in group:
|
|
1022
1652
|
logger.error("Group needs a name. Skipping...")
|
|
1653
|
+
success = False
|
|
1023
1654
|
continue
|
|
1024
1655
|
group_name = group["name"]
|
|
1025
1656
|
|
|
@@ -1035,70 +1666,35 @@ class Payload(object):
|
|
|
1035
1666
|
|
|
1036
1667
|
# Check if the group does already exist (e.g. if job is restarted)
|
|
1037
1668
|
# as this is a pattern search it could return multiple groups:
|
|
1038
|
-
|
|
1039
|
-
if
|
|
1040
|
-
logger.
|
|
1041
|
-
"Found existing
|
|
1042
|
-
|
|
1043
|
-
# Get list of all matching groups:
|
|
1044
|
-
existing_groups_list = existing_groups["data"]
|
|
1045
|
-
# Find the group with the exact match of the name:
|
|
1046
|
-
existing_group = next(
|
|
1047
|
-
(
|
|
1048
|
-
item
|
|
1049
|
-
for item in existing_groups_list
|
|
1050
|
-
if item["name"] == group_name
|
|
1051
|
-
),
|
|
1052
|
-
None,
|
|
1053
|
-
)
|
|
1054
|
-
# Have we found an exact match?
|
|
1055
|
-
if existing_group is not None:
|
|
1056
|
-
logger.info(
|
|
1057
|
-
"Found existing group -> {} ({}) - skipping to next group...".format(
|
|
1058
|
-
existing_group["name"], existing_group["id"]
|
|
1059
|
-
)
|
|
1669
|
+
group_id = self.determineGroupID(group)
|
|
1670
|
+
if group_id:
|
|
1671
|
+
logger.info(
|
|
1672
|
+
"Found existing group -> {} ({}) - skipping to next group...".format(
|
|
1673
|
+
group_name, group_id
|
|
1060
1674
|
)
|
|
1061
|
-
|
|
1675
|
+
)
|
|
1676
|
+
continue
|
|
1677
|
+
|
|
1678
|
+
logger.info("Did not find an existing group - creating a new group...")
|
|
1062
1679
|
|
|
1063
|
-
# Add Group with its ID to the class dict _placeholder_values:
|
|
1064
|
-
self._placeholder_values[
|
|
1065
|
-
"OTCS_GROUP_ID_{}".format(
|
|
1066
|
-
group_name.upper().replace(" & ", "_").replace(" ", "_")
|
|
1067
|
-
)
|
|
1068
|
-
] = str(group["id"])
|
|
1069
|
-
continue
|
|
1070
|
-
else:
|
|
1071
|
-
logger.info(
|
|
1072
|
-
"Did not find an exact match for the group - creating a new group..."
|
|
1073
|
-
)
|
|
1074
|
-
else:
|
|
1075
|
-
logger.info("Did not find any matching group - creating a new group...")
|
|
1076
1680
|
# Now we know it is a new group...
|
|
1077
1681
|
new_group = self._otcs.addGroup(group_name)
|
|
1078
1682
|
if new_group is not None:
|
|
1079
1683
|
logger.debug("New group -> {}".format(new_group))
|
|
1080
1684
|
group["id"] = new_group["id"]
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
group_name.upper().replace(" & ", "_").replace(" ", "_")
|
|
1086
|
-
)
|
|
1087
|
-
] = str(group["id"])
|
|
1685
|
+
else:
|
|
1686
|
+
logger.error("Failed to create group -> {}".format(new_group))
|
|
1687
|
+
success = False
|
|
1688
|
+
continue
|
|
1088
1689
|
|
|
1089
1690
|
logger.debug("Groups = {}".format(self._groups))
|
|
1090
1691
|
|
|
1091
|
-
logger.debug(
|
|
1092
|
-
"Placeholder values after group processing = {}".format(
|
|
1093
|
-
self._placeholder_values
|
|
1094
|
-
)
|
|
1095
|
-
)
|
|
1096
|
-
|
|
1097
1692
|
# Second run through groups: create all group memberships
|
|
1098
1693
|
# (nested groups) based on the IDs created in first run:
|
|
1099
1694
|
for group in self._groups:
|
|
1100
1695
|
if not "id" in group:
|
|
1101
1696
|
logger.error("Group -> {} does not have an ID.".format(group["name"]))
|
|
1697
|
+
success = False
|
|
1102
1698
|
continue
|
|
1103
1699
|
parent_group_names = group["parent_groups"]
|
|
1104
1700
|
for parent_group_name in parent_group_names:
|
|
@@ -1122,6 +1718,7 @@ class Payload(object):
|
|
|
1122
1718
|
parent_group_name
|
|
1123
1719
|
)
|
|
1124
1720
|
)
|
|
1721
|
+
success = False
|
|
1125
1722
|
continue
|
|
1126
1723
|
parent_group = parent_group["data"][0]
|
|
1127
1724
|
elif not "id" in parent_group:
|
|
@@ -1130,6 +1727,7 @@ class Payload(object):
|
|
|
1130
1727
|
parent_group["name"]
|
|
1131
1728
|
)
|
|
1132
1729
|
)
|
|
1730
|
+
success = False
|
|
1133
1731
|
continue
|
|
1134
1732
|
|
|
1135
1733
|
# retrieve all members of the parent group (1 = get only groups)
|
|
@@ -1155,22 +1753,41 @@ class Payload(object):
|
|
|
1155
1753
|
)
|
|
1156
1754
|
self._otcs.addGroupMember(group["id"], parent_group["id"])
|
|
1157
1755
|
|
|
1756
|
+
if success:
|
|
1757
|
+
self.writeStatusFile(section_name, self._groups)
|
|
1758
|
+
|
|
1759
|
+
return success
|
|
1760
|
+
|
|
1158
1761
|
# end method definition
|
|
1159
1762
|
|
|
1160
|
-
def processGroupsM365(self):
|
|
1763
|
+
def processGroupsM365(self, section_name: str = "groupsM365") -> bool:
|
|
1161
1764
|
"""Process groups in payload and create them in Microsoft 365.
|
|
1162
1765
|
|
|
1163
1766
|
Args:
|
|
1164
1767
|
None
|
|
1165
1768
|
Returns:
|
|
1166
|
-
|
|
1769
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
1167
1770
|
"""
|
|
1168
1771
|
|
|
1772
|
+
if not self._groups:
|
|
1773
|
+
logger.info(
|
|
1774
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
1775
|
+
)
|
|
1776
|
+
return True
|
|
1777
|
+
|
|
1778
|
+
# if this payload section has been processed successfully before we can return True
|
|
1779
|
+
# and skip processing once more
|
|
1780
|
+
if self.checkStatusFile(section_name):
|
|
1781
|
+
return True
|
|
1782
|
+
|
|
1783
|
+
success: bool = True
|
|
1784
|
+
|
|
1169
1785
|
# First run through groups: create all groups in payload
|
|
1170
1786
|
# and store the IDs of the created groups:
|
|
1171
1787
|
for group in self._groups:
|
|
1172
1788
|
if not "name" in group:
|
|
1173
1789
|
logger.error("Group needs a name. Skipping...")
|
|
1790
|
+
success = False
|
|
1174
1791
|
continue
|
|
1175
1792
|
group_name = group["name"]
|
|
1176
1793
|
|
|
@@ -1239,27 +1856,48 @@ class Payload(object):
|
|
|
1239
1856
|
group_name, group["m365_id"]
|
|
1240
1857
|
)
|
|
1241
1858
|
)
|
|
1859
|
+
else:
|
|
1860
|
+
success = False
|
|
1861
|
+
|
|
1862
|
+
if success:
|
|
1863
|
+
self.writeStatusFile(section_name, self._groups)
|
|
1864
|
+
|
|
1865
|
+
return success
|
|
1242
1866
|
|
|
1243
1867
|
# end method definition
|
|
1244
1868
|
|
|
1245
|
-
def processUsers(self):
|
|
1869
|
+
def processUsers(self, section_name: str = "users") -> bool:
|
|
1246
1870
|
"""Process users in payload and create them in Extended ECM.
|
|
1247
1871
|
|
|
1248
1872
|
Args:
|
|
1249
1873
|
None
|
|
1250
1874
|
Returns:
|
|
1251
|
-
|
|
1875
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
1252
1876
|
Side Effects:
|
|
1253
1877
|
the user items are modified by adding an "id" dict element that
|
|
1254
1878
|
includes the technical ID of the user in Extended ECM
|
|
1255
1879
|
"""
|
|
1256
1880
|
|
|
1881
|
+
if not self._users:
|
|
1882
|
+
logger.info(
|
|
1883
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
1884
|
+
)
|
|
1885
|
+
return True
|
|
1886
|
+
|
|
1887
|
+
# if this payload section has been processed successfully before we can return True
|
|
1888
|
+
# and skip processing once more
|
|
1889
|
+
if self.checkStatusFile(section_name):
|
|
1890
|
+
return True
|
|
1891
|
+
|
|
1892
|
+
success: bool = True
|
|
1893
|
+
|
|
1257
1894
|
# Add all users in payload and establish membership in
|
|
1258
1895
|
# specified groups:
|
|
1259
1896
|
for user in self._users:
|
|
1260
1897
|
# Sanity checks:
|
|
1261
1898
|
if not "name" in user:
|
|
1262
1899
|
logger.error("User is missing a login - skipping to next user...")
|
|
1900
|
+
success = False
|
|
1263
1901
|
continue
|
|
1264
1902
|
user_name = user["name"]
|
|
1265
1903
|
|
|
@@ -1278,6 +1916,7 @@ class Payload(object):
|
|
|
1278
1916
|
user_name
|
|
1279
1917
|
)
|
|
1280
1918
|
)
|
|
1919
|
+
success = False
|
|
1281
1920
|
continue
|
|
1282
1921
|
|
|
1283
1922
|
# Sanity checks:
|
|
@@ -1290,41 +1929,24 @@ class Payload(object):
|
|
|
1290
1929
|
user["base_group"] = "DefaultGroup"
|
|
1291
1930
|
|
|
1292
1931
|
# Check if the user does already exist (e.g. if job is restarted)
|
|
1293
|
-
#
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
existing_users_list = existing_users["data"]
|
|
1301
|
-
# Find the user with the exact match of the name:
|
|
1302
|
-
existing_user = next(
|
|
1303
|
-
(item for item in existing_users_list if item["name"] == user_name),
|
|
1304
|
-
None,
|
|
1305
|
-
)
|
|
1306
|
-
# Have we found an exact match?
|
|
1307
|
-
if existing_user is not None:
|
|
1308
|
-
logger.info(
|
|
1309
|
-
"Found existing user -> {} ({}) - skipping to next user...".format(
|
|
1310
|
-
existing_user["name"], existing_user["id"]
|
|
1311
|
-
)
|
|
1312
|
-
)
|
|
1313
|
-
user["id"] = existing_user["id"]
|
|
1314
|
-
continue
|
|
1315
|
-
else:
|
|
1316
|
-
logger.info(
|
|
1317
|
-
"Did not find an exact match for the user - creating a new user..."
|
|
1932
|
+
# determineUserID() also writes back the user ID into the payload
|
|
1933
|
+
# if it has gathered it from OTCS.
|
|
1934
|
+
user_id = self.determineUserID(user)
|
|
1935
|
+
if user_id:
|
|
1936
|
+
logger.info(
|
|
1937
|
+
"Found existing user -> {} ({}) - skipping to next user...".format(
|
|
1938
|
+
user_name, user_id
|
|
1318
1939
|
)
|
|
1319
|
-
|
|
1320
|
-
|
|
1940
|
+
)
|
|
1941
|
+
continue
|
|
1942
|
+
logger.info("Did not find an existing user - creating a new user...")
|
|
1321
1943
|
|
|
1322
1944
|
# Find the base group of the user. Assume 'Default Group' (= 1001) if not found:
|
|
1323
1945
|
base_group = next(
|
|
1324
1946
|
(
|
|
1325
1947
|
item["id"]
|
|
1326
1948
|
for item in self._groups
|
|
1327
|
-
if item["name"] == user["base_group"]
|
|
1949
|
+
if item["name"] == user["base_group"] and item.get("id")
|
|
1328
1950
|
),
|
|
1329
1951
|
1001,
|
|
1330
1952
|
)
|
|
@@ -1365,6 +1987,7 @@ class Payload(object):
|
|
|
1365
1987
|
logger.error(
|
|
1366
1988
|
"Group -> {} not found. Skipping...".format(group_name)
|
|
1367
1989
|
)
|
|
1990
|
+
success = False
|
|
1368
1991
|
continue
|
|
1369
1992
|
group = group["data"][0]
|
|
1370
1993
|
|
|
@@ -1374,6 +1997,7 @@ class Payload(object):
|
|
|
1374
1997
|
group["name"]
|
|
1375
1998
|
)
|
|
1376
1999
|
)
|
|
2000
|
+
success = False
|
|
1377
2001
|
continue
|
|
1378
2002
|
|
|
1379
2003
|
logger.info(
|
|
@@ -1381,10 +2005,14 @@ class Payload(object):
|
|
|
1381
2005
|
user["name"], user["id"], group["name"], group["id"]
|
|
1382
2006
|
)
|
|
1383
2007
|
)
|
|
1384
|
-
self._otcs.addGroupMember(user["id"], group["id"])
|
|
2008
|
+
response = self._otcs.addGroupMember(user["id"], group["id"])
|
|
2009
|
+
if not response:
|
|
2010
|
+
success = False
|
|
1385
2011
|
# for some unclear reason the user is not added to its base group in OTDS
|
|
1386
2012
|
# so we do this explicitly:
|
|
1387
|
-
self._otds.addUserToGroup(user["name"], user["base_group"])
|
|
2013
|
+
response = self._otds.addUserToGroup(user["name"], user["base_group"])
|
|
2014
|
+
if not response:
|
|
2015
|
+
success = False
|
|
1388
2016
|
|
|
1389
2017
|
# Extra OTDS attributes for the user can be provided in "extra_attributes"
|
|
1390
2018
|
# as part of the user payload.
|
|
@@ -1397,6 +2025,7 @@ class Payload(object):
|
|
|
1397
2025
|
logger.error(
|
|
1398
2026
|
"User attribute is missing a name or value. Skipping..."
|
|
1399
2027
|
)
|
|
2028
|
+
success = False
|
|
1400
2029
|
continue
|
|
1401
2030
|
logger.info(
|
|
1402
2031
|
"Set user attribute -> {} to -> {}".format(
|
|
@@ -1406,6 +2035,8 @@ class Payload(object):
|
|
|
1406
2035
|
user_partition = self._otcs.config()["partition"]
|
|
1407
2036
|
if not user_partition:
|
|
1408
2037
|
logger.error("User partition not found!")
|
|
2038
|
+
success = False
|
|
2039
|
+
continue
|
|
1409
2040
|
self._otds.updateUser(
|
|
1410
2041
|
user_partition,
|
|
1411
2042
|
user["name"],
|
|
@@ -1413,23 +2044,42 @@ class Payload(object):
|
|
|
1413
2044
|
attribute_value,
|
|
1414
2045
|
)
|
|
1415
2046
|
|
|
2047
|
+
if success:
|
|
2048
|
+
self.writeStatusFile(section_name, self._users)
|
|
2049
|
+
|
|
2050
|
+
return success
|
|
2051
|
+
|
|
1416
2052
|
# end method definition
|
|
1417
2053
|
|
|
1418
|
-
def processUsersM365(self):
|
|
2054
|
+
def processUsersM365(self, section_name: str = "usersM365") -> bool:
|
|
1419
2055
|
"""Process users in payload and create them in Microsoft 365 via MS Graph API.
|
|
1420
2056
|
|
|
1421
2057
|
Args:
|
|
1422
2058
|
None
|
|
1423
2059
|
Returns:
|
|
1424
|
-
|
|
2060
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
1425
2061
|
"""
|
|
1426
2062
|
|
|
2063
|
+
if not self._users:
|
|
2064
|
+
logger.info(
|
|
2065
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
2066
|
+
)
|
|
2067
|
+
return True
|
|
2068
|
+
|
|
2069
|
+
# if this payload section has been processed successfully before we can return True
|
|
2070
|
+
# and skip processing once more
|
|
2071
|
+
if self.checkStatusFile(section_name):
|
|
2072
|
+
return True
|
|
2073
|
+
|
|
2074
|
+
success: bool = True
|
|
2075
|
+
|
|
1427
2076
|
# Add all users in payload and establish membership in
|
|
1428
2077
|
# specified groups:
|
|
1429
2078
|
for user in self._users:
|
|
1430
2079
|
# Sanity checks:
|
|
1431
2080
|
if not "name" in user:
|
|
1432
2081
|
logger.error("User is missing a login - skipping to next user...")
|
|
2082
|
+
success = False
|
|
1433
2083
|
continue
|
|
1434
2084
|
user_name = user["name"]
|
|
1435
2085
|
|
|
@@ -1455,6 +2105,7 @@ class Payload(object):
|
|
|
1455
2105
|
user_name
|
|
1456
2106
|
)
|
|
1457
2107
|
)
|
|
2108
|
+
success = False
|
|
1458
2109
|
continue
|
|
1459
2110
|
user_password = user["password"]
|
|
1460
2111
|
# be careful with the following fields - they could be empty
|
|
@@ -1504,6 +2155,7 @@ class Payload(object):
|
|
|
1504
2155
|
user_name
|
|
1505
2156
|
)
|
|
1506
2157
|
)
|
|
2158
|
+
success = False
|
|
1507
2159
|
continue
|
|
1508
2160
|
|
|
1509
2161
|
# Now we assign a license to the new M365 user.
|
|
@@ -1524,6 +2176,7 @@ class Payload(object):
|
|
|
1524
2176
|
sku_id, user_name
|
|
1525
2177
|
)
|
|
1526
2178
|
)
|
|
2179
|
+
success = False
|
|
1527
2180
|
else:
|
|
1528
2181
|
if (
|
|
1529
2182
|
not "m365_skus" in user
|
|
@@ -1579,7 +2232,7 @@ class Payload(object):
|
|
|
1579
2232
|
user_name, group_names, user_department
|
|
1580
2233
|
)
|
|
1581
2234
|
)
|
|
1582
|
-
# Go through all
|
|
2235
|
+
# Go through all group names:
|
|
1583
2236
|
for group_name in group_names:
|
|
1584
2237
|
# Find the group payload item to the parent group name:
|
|
1585
2238
|
group = next(
|
|
@@ -1620,6 +2273,7 @@ class Payload(object):
|
|
|
1620
2273
|
group_name
|
|
1621
2274
|
)
|
|
1622
2275
|
)
|
|
2276
|
+
success = False
|
|
1623
2277
|
else:
|
|
1624
2278
|
group_id = response["value"][0]["id"]
|
|
1625
2279
|
|
|
@@ -1694,6 +2348,7 @@ class Payload(object):
|
|
|
1694
2348
|
group_name
|
|
1695
2349
|
)
|
|
1696
2350
|
)
|
|
2351
|
+
success = False
|
|
1697
2352
|
continue
|
|
1698
2353
|
parent_group_id = response["value"][0]["id"]
|
|
1699
2354
|
|
|
@@ -1722,9 +2377,14 @@ class Payload(object):
|
|
|
1722
2377
|
)
|
|
1723
2378
|
self._m365.addGroupMember(parent_group_id, user_id)
|
|
1724
2379
|
|
|
2380
|
+
if success:
|
|
2381
|
+
self.writeStatusFile(section_name, self._users)
|
|
2382
|
+
|
|
2383
|
+
return success
|
|
2384
|
+
|
|
1725
2385
|
# end method definition
|
|
1726
2386
|
|
|
1727
|
-
def processTeamsM365(self):
|
|
2387
|
+
def processTeamsM365(self, section_name: str = "teamsM365") -> bool:
|
|
1728
2388
|
"""Process groups in payload and create matching Teams in Microsoft 365.
|
|
1729
2389
|
We need to do this after the creation of the M365 users as wie require
|
|
1730
2390
|
Group Owners to create teams.
|
|
@@ -1732,12 +2392,26 @@ class Payload(object):
|
|
|
1732
2392
|
Args:
|
|
1733
2393
|
None
|
|
1734
2394
|
Returns:
|
|
1735
|
-
|
|
2395
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
1736
2396
|
"""
|
|
1737
2397
|
|
|
2398
|
+
if not self._groups:
|
|
2399
|
+
logger.info(
|
|
2400
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
2401
|
+
)
|
|
2402
|
+
return True
|
|
2403
|
+
|
|
2404
|
+
# if this payload section has been processed successfully before we can return True
|
|
2405
|
+
# and skip processing once more
|
|
2406
|
+
if self.checkStatusFile(section_name):
|
|
2407
|
+
return True
|
|
2408
|
+
|
|
2409
|
+
success: bool = True
|
|
2410
|
+
|
|
1738
2411
|
for group in self._groups:
|
|
1739
2412
|
if not "name" in group:
|
|
1740
2413
|
logger.error("Team needs a name. Skipping...")
|
|
2414
|
+
success = False
|
|
1741
2415
|
continue
|
|
1742
2416
|
group_name = group["name"]
|
|
1743
2417
|
|
|
@@ -1766,6 +2440,7 @@ class Payload(object):
|
|
|
1766
2440
|
group_name
|
|
1767
2441
|
)
|
|
1768
2442
|
)
|
|
2443
|
+
success = False
|
|
1769
2444
|
continue
|
|
1770
2445
|
|
|
1771
2446
|
if not self._m365.hasTeam(group_name):
|
|
@@ -1776,6 +2451,8 @@ class Payload(object):
|
|
|
1776
2451
|
)
|
|
1777
2452
|
# Now "upgrading" this group to a MS Team:
|
|
1778
2453
|
new_team = self._m365.addTeam(group_name)
|
|
2454
|
+
if not new_team:
|
|
2455
|
+
success = False
|
|
1779
2456
|
else:
|
|
1780
2457
|
logger.info(
|
|
1781
2458
|
"M365 group -> {} already has an MS Team connected. Skipping...".format(
|
|
@@ -1783,6 +2460,11 @@ class Payload(object):
|
|
|
1783
2460
|
)
|
|
1784
2461
|
)
|
|
1785
2462
|
|
|
2463
|
+
if success:
|
|
2464
|
+
self.writeStatusFile(section_name, self._groups)
|
|
2465
|
+
|
|
2466
|
+
return success
|
|
2467
|
+
|
|
1786
2468
|
# end method definition
|
|
1787
2469
|
|
|
1788
2470
|
def cleanupStaleTeamsM365(self, workspace_types: list):
|
|
@@ -1825,35 +2507,86 @@ class Payload(object):
|
|
|
1825
2507
|
|
|
1826
2508
|
# end method definition
|
|
1827
2509
|
|
|
1828
|
-
def cleanupAllTeamsM365(self) -> bool:
|
|
2510
|
+
def cleanupAllTeamsM365(self, section_name: str = "teamsM365Cleanup") -> bool:
|
|
1829
2511
|
"""Delete Microsoft Teams that are left-overs from former deployments
|
|
1830
2512
|
|
|
1831
2513
|
Args:
|
|
1832
2514
|
None
|
|
1833
2515
|
Returns:
|
|
1834
|
-
|
|
2516
|
+
bool: True if teams have been deleted, False otherwise
|
|
1835
2517
|
"""
|
|
1836
2518
|
|
|
2519
|
+
# We want this cleanup to only run once even if we have
|
|
2520
|
+
# multiple payload files - so we pass payload_specific=False here:
|
|
2521
|
+
if self.checkStatusFile(
|
|
2522
|
+
payload_section_name=section_name, payload_specific=False
|
|
2523
|
+
):
|
|
2524
|
+
logger.info(
|
|
2525
|
+
"Payload section -> {} has been processed successfully before. Skip cleanup of M365 teams...".format(
|
|
2526
|
+
section_name
|
|
2527
|
+
)
|
|
2528
|
+
)
|
|
2529
|
+
return True
|
|
2530
|
+
else:
|
|
2531
|
+
logger.info("Processing payload section -> {}...".format(section_name))
|
|
2532
|
+
|
|
2533
|
+
# We don't want to delete MS Teams that are matching the regular OTCS Group Names (like "Sales")
|
|
1837
2534
|
exception_list = self.getAllGroupNames()
|
|
1838
|
-
|
|
2535
|
+
|
|
2536
|
+
# These are the patterns that each MS Teams needs to match at least one of to be deleted
|
|
2537
|
+
# Pattern 1: all MS teams with a name that has a number in brackets, line "(1234)"
|
|
2538
|
+
# Pattern 2: all MS Teams with a name that starts with a number followed by a space,
|
|
2539
|
+
# followed by a "- and followed by another space
|
|
2540
|
+
# Pattern 3: all MS Teams with a name that starts with "WS" and a 1-4 digit number
|
|
2541
|
+
# (these are the workspaces for Purchase Contracts generated for Intelligent Filing)
|
|
2542
|
+
# Pattern 4: all MS Teams with a name that ends with a 1-2 character + a number in brackets, like (US-1000)
|
|
2543
|
+
# this is a specialization of pattern 1
|
|
2544
|
+
pattern_list = [
|
|
2545
|
+
r"\(\d+\)",
|
|
2546
|
+
r"\d+\s-\s",
|
|
2547
|
+
r"^WS\d{1,4}$",
|
|
2548
|
+
r"^.+?\s\(.{1,2}-\d+\)$",
|
|
2549
|
+
]
|
|
1839
2550
|
|
|
1840
2551
|
result = self._m365.deleteAllTeams(exception_list, pattern_list)
|
|
1841
2552
|
|
|
2553
|
+
# We want this cleanup to only run once even if we have
|
|
2554
|
+
# multiple payload files - so we pass payload_specific=False here:
|
|
2555
|
+
self.writeStatusFile(
|
|
2556
|
+
payload_section_name=section_name,
|
|
2557
|
+
payload_section=exception_list + pattern_list,
|
|
2558
|
+
payload_specific=False,
|
|
2559
|
+
)
|
|
2560
|
+
|
|
1842
2561
|
return result
|
|
1843
2562
|
|
|
1844
2563
|
# end method definition
|
|
1845
2564
|
|
|
1846
|
-
def processAdminSettings(
|
|
2565
|
+
def processAdminSettings(
|
|
2566
|
+
self, admin_settings: list, section_name: str = "adminSettings"
|
|
2567
|
+
) -> bool:
|
|
1847
2568
|
"""Process admin settings in payload and import them to Extended ECM.
|
|
1848
2569
|
|
|
1849
2570
|
Args:
|
|
1850
2571
|
admin_settings (list): list of admin settings. We need this parameter
|
|
1851
2572
|
as we process two different lists.
|
|
1852
2573
|
Returns:
|
|
1853
|
-
|
|
2574
|
+
bool: True if a restart of the OTCS pods is required. False otherwise.
|
|
1854
2575
|
"""
|
|
1855
2576
|
|
|
1856
|
-
|
|
2577
|
+
if not admin_settings:
|
|
2578
|
+
logger.info(
|
|
2579
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
2580
|
+
)
|
|
2581
|
+
return True
|
|
2582
|
+
|
|
2583
|
+
# if this payload section has been processed successfully before we can return True
|
|
2584
|
+
# and skip processing once more
|
|
2585
|
+
if self.checkStatusFile(section_name):
|
|
2586
|
+
return True
|
|
2587
|
+
|
|
2588
|
+
restart_required: bool = False
|
|
2589
|
+
success: bool = True
|
|
1857
2590
|
|
|
1858
2591
|
for admin_setting in admin_settings:
|
|
1859
2592
|
# Sanity checks:
|
|
@@ -1903,23 +2636,40 @@ class Payload(object):
|
|
|
1903
2636
|
logger.error(
|
|
1904
2637
|
"Admin settings file -> {} not found.".format(settings_file)
|
|
1905
2638
|
)
|
|
2639
|
+
success = False
|
|
2640
|
+
|
|
2641
|
+
if success:
|
|
2642
|
+
self.writeStatusFile(section_name, admin_settings)
|
|
1906
2643
|
|
|
1907
2644
|
return restart_required
|
|
1908
2645
|
|
|
1909
2646
|
# end method definition
|
|
1910
2647
|
|
|
1911
|
-
def processExternalSystems(self):
|
|
2648
|
+
def processExternalSystems(self, section_name: str = "externalSystems") -> bool:
|
|
1912
2649
|
"""Process external systems in payload and create them in Extended ECM.
|
|
1913
2650
|
|
|
1914
2651
|
Args:
|
|
1915
2652
|
None
|
|
1916
2653
|
Returns:
|
|
1917
|
-
|
|
2654
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
1918
2655
|
Side Effects:
|
|
1919
2656
|
- based on system_type different other settings in the dict are set
|
|
1920
2657
|
- reachability is tested and a flag is set in the dict are set
|
|
1921
2658
|
"""
|
|
1922
2659
|
|
|
2660
|
+
if not self._external_systems:
|
|
2661
|
+
logger.info(
|
|
2662
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
2663
|
+
)
|
|
2664
|
+
return True
|
|
2665
|
+
|
|
2666
|
+
# if this payload section has been processed successfully before we can return True
|
|
2667
|
+
# and skip processing once more
|
|
2668
|
+
if self.checkStatusFile(section_name):
|
|
2669
|
+
return True
|
|
2670
|
+
|
|
2671
|
+
success: bool = True
|
|
2672
|
+
|
|
1923
2673
|
for external_system in self._external_systems:
|
|
1924
2674
|
#
|
|
1925
2675
|
# 1: Do sanity checks for the payload:
|
|
@@ -1928,6 +2678,7 @@ class Payload(object):
|
|
|
1928
2678
|
logger.error(
|
|
1929
2679
|
"External System connection needs a logical system name! Skipping to next external system..."
|
|
1930
2680
|
)
|
|
2681
|
+
success = False
|
|
1931
2682
|
continue
|
|
1932
2683
|
system_name = external_system["external_system_name"]
|
|
1933
2684
|
|
|
@@ -1937,6 +2688,7 @@ class Payload(object):
|
|
|
1937
2688
|
system_name
|
|
1938
2689
|
)
|
|
1939
2690
|
)
|
|
2691
|
+
success = False
|
|
1940
2692
|
continue
|
|
1941
2693
|
system_type = external_system["external_system_type"]
|
|
1942
2694
|
|
|
@@ -1954,6 +2706,15 @@ class Payload(object):
|
|
|
1954
2706
|
else ""
|
|
1955
2707
|
)
|
|
1956
2708
|
|
|
2709
|
+
# Possible Connection Types for external systems:
|
|
2710
|
+
# "Business Scenario Sample" (Business Scenarios Sample Adapter)
|
|
2711
|
+
# "ot.sap.c4c.SpiAdapter" (SAP C4C SPI Adapter)
|
|
2712
|
+
# "ot.sap.c4c.SpiAdapterV2" (C4C SPI Adapter V2)
|
|
2713
|
+
# "HTTP" (Default WebService Adapter)
|
|
2714
|
+
# "ot.sap.S4HANAAdapter" (S/4HANA SPI Adapter)
|
|
2715
|
+
# "SF" (SalesForce Adapter)
|
|
2716
|
+
# "SFInstance" (SFWebService)
|
|
2717
|
+
|
|
1957
2718
|
# Set the default settings for the different system types:
|
|
1958
2719
|
match system_type:
|
|
1959
2720
|
# Check if we have a SuccessFactors system:
|
|
@@ -1993,6 +2754,7 @@ class Payload(object):
|
|
|
1993
2754
|
system_name
|
|
1994
2755
|
)
|
|
1995
2756
|
)
|
|
2757
|
+
success = False
|
|
1996
2758
|
continue
|
|
1997
2759
|
as_url = external_system["as_url"]
|
|
1998
2760
|
|
|
@@ -2045,6 +2807,7 @@ class Payload(object):
|
|
|
2045
2807
|
system_name
|
|
2046
2808
|
)
|
|
2047
2809
|
)
|
|
2810
|
+
success = False
|
|
2048
2811
|
continue
|
|
2049
2812
|
if not "oauth_client_secret" in external_system:
|
|
2050
2813
|
logger.error(
|
|
@@ -2052,6 +2815,7 @@ class Payload(object):
|
|
|
2052
2815
|
system_name
|
|
2053
2816
|
)
|
|
2054
2817
|
)
|
|
2818
|
+
success = False
|
|
2055
2819
|
continue
|
|
2056
2820
|
oauth_client_id = external_system["oauth_client_id"]
|
|
2057
2821
|
oauth_client_secret = external_system["oauth_client_secret"]
|
|
@@ -2108,6 +2872,7 @@ class Payload(object):
|
|
|
2108
2872
|
system_name, connection_type
|
|
2109
2873
|
)
|
|
2110
2874
|
)
|
|
2875
|
+
success = False
|
|
2111
2876
|
else:
|
|
2112
2877
|
logger.info(
|
|
2113
2878
|
"Successfully created external system -> {}".format(system_name)
|
|
@@ -2127,6 +2892,7 @@ class Payload(object):
|
|
|
2127
2892
|
connection_type,
|
|
2128
2893
|
)
|
|
2129
2894
|
)
|
|
2895
|
+
success = False
|
|
2130
2896
|
continue
|
|
2131
2897
|
saml_url = external_system["saml_url"]
|
|
2132
2898
|
if not "otds_sp_endpoint" in external_system:
|
|
@@ -2136,6 +2902,7 @@ class Payload(object):
|
|
|
2136
2902
|
connection_type,
|
|
2137
2903
|
)
|
|
2138
2904
|
)
|
|
2905
|
+
success = False
|
|
2139
2906
|
continue
|
|
2140
2907
|
otds_sp_endpoint = external_system["otds_sp_endpoint"]
|
|
2141
2908
|
|
|
@@ -2150,6 +2917,7 @@ class Payload(object):
|
|
|
2150
2917
|
logger.info("Successfully added SAML authentication handler.")
|
|
2151
2918
|
else:
|
|
2152
2919
|
logger.error("Failed to add SAML authentication handler.")
|
|
2920
|
+
success = False
|
|
2153
2921
|
case "SAP":
|
|
2154
2922
|
# Configure a certificate-based SAP authentication handler:
|
|
2155
2923
|
if not "certificate_file" in external_system:
|
|
@@ -2167,6 +2935,7 @@ class Payload(object):
|
|
|
2167
2935
|
external_system["certificate_file"],
|
|
2168
2936
|
)
|
|
2169
2937
|
)
|
|
2938
|
+
success = False
|
|
2170
2939
|
continue
|
|
2171
2940
|
certificate_file = external_system["certificate_file"]
|
|
2172
2941
|
certificate_password = external_system["certificate_password"]
|
|
@@ -2181,6 +2950,7 @@ class Payload(object):
|
|
|
2181
2950
|
logger.info("Successfully added SAP authentication handler.")
|
|
2182
2951
|
else:
|
|
2183
2952
|
logger.error("Failed to add SAP authentication handler.")
|
|
2953
|
+
success = False
|
|
2184
2954
|
# Upload and enable certificate file for Archive Center that is required for SAP scenarios
|
|
2185
2955
|
# we only do this if the necessary information is in payload and if OTAC is enabled:
|
|
2186
2956
|
if (
|
|
@@ -2219,6 +2989,7 @@ class Payload(object):
|
|
|
2219
2989
|
connection_type,
|
|
2220
2990
|
)
|
|
2221
2991
|
)
|
|
2992
|
+
success = False
|
|
2222
2993
|
continue
|
|
2223
2994
|
authorization_endpoint = external_system["authorization_endpoint"]
|
|
2224
2995
|
if not "token_endpoint" in external_system:
|
|
@@ -2228,6 +2999,7 @@ class Payload(object):
|
|
|
2228
2999
|
connection_type,
|
|
2229
3000
|
)
|
|
2230
3001
|
)
|
|
3002
|
+
success = False
|
|
2231
3003
|
continue
|
|
2232
3004
|
token_endpoint = external_system["token_endpoint"]
|
|
2233
3005
|
response = self._otds.addAuthHandlerOAuth(
|
|
@@ -2244,11 +3016,19 @@ class Payload(object):
|
|
|
2244
3016
|
if response:
|
|
2245
3017
|
logger.info("Successfully added OAuth authentication handler.")
|
|
2246
3018
|
else:
|
|
3019
|
+
success = False
|
|
2247
3020
|
logger.error("Failed to add OAuth authentication handler.")
|
|
2248
3021
|
|
|
3022
|
+
if success:
|
|
3023
|
+
self.writeStatusFile(section_name, self._external_systems)
|
|
3024
|
+
|
|
3025
|
+
return success
|
|
3026
|
+
|
|
2249
3027
|
# end method definition
|
|
2250
3028
|
|
|
2251
|
-
def processTransportPackages(
|
|
3029
|
+
def processTransportPackages(
|
|
3030
|
+
self, transport_packages: list, section_name: str = "transportPackages"
|
|
3031
|
+
) -> bool:
|
|
2252
3032
|
"""Process transport packages in payload and import them to Extended ECM.
|
|
2253
3033
|
|
|
2254
3034
|
Args:
|
|
@@ -2257,14 +3037,28 @@ class Payload(object):
|
|
|
2257
3037
|
content_transport, transport_post) so
|
|
2258
3038
|
we need a parameter
|
|
2259
3039
|
Returns:
|
|
2260
|
-
|
|
3040
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
2261
3041
|
"""
|
|
2262
3042
|
|
|
3043
|
+
if not transport_packages:
|
|
3044
|
+
logger.info(
|
|
3045
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
3046
|
+
)
|
|
3047
|
+
return True
|
|
3048
|
+
|
|
3049
|
+
# if this payload section has been processed successfully before we can return True
|
|
3050
|
+
# and skip processing once more
|
|
3051
|
+
if self.checkStatusFile(section_name):
|
|
3052
|
+
return True
|
|
3053
|
+
|
|
3054
|
+
success: bool = True
|
|
3055
|
+
|
|
2263
3056
|
for transport_package in transport_packages:
|
|
2264
3057
|
if not "name" in transport_package:
|
|
2265
3058
|
logger.error(
|
|
2266
3059
|
"Transport Package needs a name! Skipping to next transport..."
|
|
2267
3060
|
)
|
|
3061
|
+
success = False
|
|
2268
3062
|
continue
|
|
2269
3063
|
name = transport_package["name"]
|
|
2270
3064
|
|
|
@@ -2282,6 +3076,7 @@ class Payload(object):
|
|
|
2282
3076
|
name
|
|
2283
3077
|
)
|
|
2284
3078
|
)
|
|
3079
|
+
success = False
|
|
2285
3080
|
continue
|
|
2286
3081
|
if not "description" in transport_package:
|
|
2287
3082
|
logger.warning(
|
|
@@ -2311,22 +3106,41 @@ class Payload(object):
|
|
|
2311
3106
|
logger.error(
|
|
2312
3107
|
"Failed to deploy transport -> {}; URL -> {}".format(name, url)
|
|
2313
3108
|
)
|
|
3109
|
+
success = False
|
|
2314
3110
|
if self._stop_on_error:
|
|
2315
|
-
|
|
3111
|
+
break
|
|
2316
3112
|
else:
|
|
2317
3113
|
logger.info("Successfully deployed transport -> {}".format(name))
|
|
2318
3114
|
|
|
3115
|
+
if success:
|
|
3116
|
+
self.writeStatusFile(section_name, transport_packages)
|
|
3117
|
+
|
|
3118
|
+
return success
|
|
3119
|
+
|
|
2319
3120
|
# end method definition
|
|
2320
3121
|
|
|
2321
|
-
def processUserPhotos(self):
|
|
3122
|
+
def processUserPhotos(self, section_name: str = "userPhotos") -> bool:
|
|
2322
3123
|
"""Process user photos in payload and assign them to Extended ECM users.
|
|
2323
3124
|
|
|
2324
3125
|
Args:
|
|
2325
3126
|
None
|
|
2326
3127
|
Returns:
|
|
2327
|
-
|
|
3128
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
2328
3129
|
"""
|
|
2329
3130
|
|
|
3131
|
+
if not self._users:
|
|
3132
|
+
logger.info(
|
|
3133
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
3134
|
+
)
|
|
3135
|
+
return True
|
|
3136
|
+
|
|
3137
|
+
# if this payload section has been processed successfully before we can return True
|
|
3138
|
+
# and skip processing once more
|
|
3139
|
+
if self.checkStatusFile(section_name):
|
|
3140
|
+
return True
|
|
3141
|
+
|
|
3142
|
+
success: bool = True
|
|
3143
|
+
|
|
2330
3144
|
# we assume the nickname of the photo item equals the login name of the user
|
|
2331
3145
|
# we also assume that the photos have been uploaded / transported into the target system
|
|
2332
3146
|
for user in self._users:
|
|
@@ -2346,6 +3160,7 @@ class Payload(object):
|
|
|
2346
3160
|
user_name
|
|
2347
3161
|
)
|
|
2348
3162
|
)
|
|
3163
|
+
success = False
|
|
2349
3164
|
continue
|
|
2350
3165
|
|
|
2351
3166
|
user_id = user["id"]
|
|
@@ -2360,8 +3175,9 @@ class Payload(object):
|
|
|
2360
3175
|
continue
|
|
2361
3176
|
photo_id = self._otcs.getResultValue(response, "id")
|
|
2362
3177
|
response = self._otcs.updateUserPhoto(user_id, photo_id)
|
|
2363
|
-
if response
|
|
2364
|
-
logger.
|
|
3178
|
+
if not response:
|
|
3179
|
+
logger.error("Failed to add photo for user -> {}".format(user_name))
|
|
3180
|
+
success = False
|
|
2365
3181
|
else:
|
|
2366
3182
|
logger.info("Successfully added photo for user -> {}".format(user_name))
|
|
2367
3183
|
|
|
@@ -2369,25 +3185,43 @@ class Payload(object):
|
|
|
2369
3185
|
response = self._otcs.getNodeFromNickname("admin")
|
|
2370
3186
|
if response == None:
|
|
2371
3187
|
logger.warning("Missing photo for admin - nickname not found. Skipping...")
|
|
2372
|
-
return
|
|
2373
|
-
photo_id = self._otcs.getResultValue(response, "id")
|
|
2374
|
-
response = self._otcs.updateUserPhoto(1000, photo_id)
|
|
2375
|
-
if response == None:
|
|
2376
|
-
logger.warning("Failed to add photo for admin")
|
|
2377
3188
|
else:
|
|
2378
|
-
|
|
3189
|
+
photo_id = self._otcs.getResultValue(response, "id")
|
|
3190
|
+
response = self._otcs.updateUserPhoto(1000, photo_id)
|
|
3191
|
+
if response == None:
|
|
3192
|
+
logger.warning("Failed to add photo for admin")
|
|
3193
|
+
else:
|
|
3194
|
+
logger.info("Successfully added photo for admin")
|
|
3195
|
+
|
|
3196
|
+
if success:
|
|
3197
|
+
self.writeStatusFile(section_name, self._users)
|
|
3198
|
+
|
|
3199
|
+
return success
|
|
2379
3200
|
|
|
2380
3201
|
# end method definition
|
|
2381
3202
|
|
|
2382
|
-
def processUserPhotosM365(self):
|
|
3203
|
+
def processUserPhotosM365(self, section_name: str = "userPhotosM365") -> bool:
|
|
2383
3204
|
"""Process user photos in payload and assign them to Microsoft 365 users.
|
|
2384
3205
|
|
|
2385
3206
|
Args:
|
|
2386
3207
|
None
|
|
2387
3208
|
Returns:
|
|
2388
|
-
|
|
3209
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
2389
3210
|
"""
|
|
2390
3211
|
|
|
3212
|
+
if not self._users:
|
|
3213
|
+
logger.info(
|
|
3214
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
3215
|
+
)
|
|
3216
|
+
return True
|
|
3217
|
+
|
|
3218
|
+
# if this payload section has been processed successfully before we can return True
|
|
3219
|
+
# and skip processing once more
|
|
3220
|
+
if self.checkStatusFile(section_name):
|
|
3221
|
+
return True
|
|
3222
|
+
|
|
3223
|
+
success: bool = True
|
|
3224
|
+
|
|
2391
3225
|
# we assume the nickname of the photo item equals the login name of the user
|
|
2392
3226
|
# we also assume that the photos have been uploaded / transported into the target system
|
|
2393
3227
|
for user in self._users:
|
|
@@ -2398,6 +3232,7 @@ class Payload(object):
|
|
|
2398
3232
|
user_name
|
|
2399
3233
|
)
|
|
2400
3234
|
)
|
|
3235
|
+
success = False
|
|
2401
3236
|
continue
|
|
2402
3237
|
|
|
2403
3238
|
# Check if element has been disabled in payload (enabled = false).
|
|
@@ -2420,6 +3255,7 @@ class Payload(object):
|
|
|
2420
3255
|
user_name
|
|
2421
3256
|
)
|
|
2422
3257
|
)
|
|
3258
|
+
success = False
|
|
2423
3259
|
continue
|
|
2424
3260
|
|
|
2425
3261
|
user_m365_id = user["m365_id"]
|
|
@@ -2456,6 +3292,7 @@ class Payload(object):
|
|
|
2456
3292
|
user_name
|
|
2457
3293
|
)
|
|
2458
3294
|
)
|
|
3295
|
+
success = False
|
|
2459
3296
|
else:
|
|
2460
3297
|
logger.info(
|
|
2461
3298
|
"Successfully downloaded photo for user -> {} from Extended ECM to file -> {}".format(
|
|
@@ -2471,6 +3308,7 @@ class Payload(object):
|
|
|
2471
3308
|
user_name
|
|
2472
3309
|
)
|
|
2473
3310
|
)
|
|
3311
|
+
success = False
|
|
2474
3312
|
else:
|
|
2475
3313
|
logger.info(
|
|
2476
3314
|
"Successfully uploaded photo for user -> {} to Microsoft 365".format(
|
|
@@ -2478,9 +3316,14 @@ class Payload(object):
|
|
|
2478
3316
|
)
|
|
2479
3317
|
)
|
|
2480
3318
|
|
|
3319
|
+
if success:
|
|
3320
|
+
self.writeStatusFile(section_name, self._users)
|
|
3321
|
+
|
|
3322
|
+
return success
|
|
3323
|
+
|
|
2481
3324
|
# end method definition
|
|
2482
3325
|
|
|
2483
|
-
def processWorkspaceTypes(self) -> list:
|
|
3326
|
+
def processWorkspaceTypes(self, section_name: str = "workspaceTypes") -> list:
|
|
2484
3327
|
"""Create a data structure for all workspace types in the Extended ECM system.
|
|
2485
3328
|
|
|
2486
3329
|
Args:
|
|
@@ -2494,6 +3337,11 @@ class Payload(object):
|
|
|
2494
3337
|
+ id
|
|
2495
3338
|
"""
|
|
2496
3339
|
|
|
3340
|
+
# if this payload section has been processed successfully before we can return True
|
|
3341
|
+
# and skip processing once more
|
|
3342
|
+
if self.checkStatusFile(section_name):
|
|
3343
|
+
return True
|
|
3344
|
+
|
|
2497
3345
|
# get all workspace types (these have been created by the transports and are not in the payload!):
|
|
2498
3346
|
response = self._otcs.getWorkspaceTypes()
|
|
2499
3347
|
if response == None:
|
|
@@ -2569,113 +3417,36 @@ class Payload(object):
|
|
|
2569
3417
|
)
|
|
2570
3418
|
continue
|
|
2571
3419
|
|
|
3420
|
+
self.writeStatusFile(section_name, self._workspace_types)
|
|
3421
|
+
|
|
2572
3422
|
return self._workspace_types
|
|
2573
3423
|
|
|
2574
3424
|
# end method definition
|
|
2575
3425
|
|
|
2576
|
-
def
|
|
2577
|
-
"""Process
|
|
2578
|
-
This has been a work-around for a transport issue in 22.4. This code is not required
|
|
2579
|
-
for 23.1 or newer. Right now we just disabled the payload. We may consider to remove
|
|
2580
|
-
the code in future versions.
|
|
2581
|
-
|
|
2582
|
-
Args: None
|
|
2583
|
-
Return: None
|
|
2584
|
-
"""
|
|
2585
|
-
|
|
2586
|
-
# loop through the workspace template registrations in the payload:
|
|
2587
|
-
for workspace_template_registration in self._workspace_template_registrations:
|
|
2588
|
-
# Do some sanity checks first and read the workspace type name
|
|
2589
|
-
# and workspace template name from the payload:
|
|
2590
|
-
if not "workspace_type_name" in workspace_template_registration:
|
|
2591
|
-
logger.error(
|
|
2592
|
-
"Workspace template registration needs a workspace type name! Skipping to next workspace template registration..."
|
|
2593
|
-
)
|
|
2594
|
-
continue
|
|
2595
|
-
type_name = workspace_template_registration["workspace_type_name"]
|
|
2596
|
-
|
|
2597
|
-
if (
|
|
2598
|
-
"enabled" in workspace_template_registration
|
|
2599
|
-
and not workspace_template_registration["enabled"]
|
|
2600
|
-
):
|
|
2601
|
-
logger.info(
|
|
2602
|
-
"Payload for Workspace Template Registration for Workspace Type -> {} is disabled. Skipping...".format(
|
|
2603
|
-
type_name
|
|
2604
|
-
)
|
|
2605
|
-
)
|
|
2606
|
-
continue
|
|
2607
|
-
|
|
2608
|
-
if not "workspace_template_name" in workspace_template_registration:
|
|
2609
|
-
logger.error(
|
|
2610
|
-
"Workspace template registration needs a workspace template name! Skipping to next workspace template registration..."
|
|
2611
|
-
)
|
|
2612
|
-
continue
|
|
2613
|
-
template_name = workspace_template_registration["workspace_template_name"]
|
|
2614
|
-
|
|
2615
|
-
# now we have the template type name and the template name from the
|
|
2616
|
-
# payload. We can now proceed to find these items in the workspace types
|
|
2617
|
-
# data structure. First we lookup the workspace type:
|
|
2618
|
-
workspace_type = next(
|
|
2619
|
-
(item for item in self._workspace_types if item["name"] == type_name),
|
|
2620
|
-
None,
|
|
2621
|
-
)
|
|
2622
|
-
if workspace_type is None:
|
|
2623
|
-
logger.error(
|
|
2624
|
-
"Workspace Type with name -> {} not found.".format(type_name)
|
|
2625
|
-
)
|
|
2626
|
-
continue
|
|
3426
|
+
def processWorkspaces(self, section_name: str = "workspaces") -> bool:
|
|
3427
|
+
"""Process workspaces in payload and create them in Extended ECM.
|
|
2627
3428
|
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
if
|
|
2632
|
-
logger.error(
|
|
2633
|
-
"Workspace Type with name -> {} has no templates.".format(type_name)
|
|
2634
|
-
)
|
|
2635
|
-
continue
|
|
3429
|
+
Args:
|
|
3430
|
+
None
|
|
3431
|
+
Return:
|
|
3432
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
2636
3433
|
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
for item in workspace_template_list
|
|
2641
|
-
if item["name"] == template_name
|
|
2642
|
-
),
|
|
2643
|
-
None,
|
|
2644
|
-
)
|
|
2645
|
-
if workspace_template is None:
|
|
2646
|
-
logger.error(
|
|
2647
|
-
"Workspace Type with name -> {} has no Template with name -> {}.".format(
|
|
2648
|
-
type_name, template_name
|
|
2649
|
-
)
|
|
2650
|
-
)
|
|
2651
|
-
continue
|
|
2652
|
-
workspace_template_id = workspace_template["id"]
|
|
3434
|
+
Side Effects:
|
|
3435
|
+
Set workspace["nodeId] to the node ID of the created workspace
|
|
3436
|
+
"""
|
|
2653
3437
|
|
|
3438
|
+
if not self._workspaces:
|
|
2654
3439
|
logger.info(
|
|
2655
|
-
"
|
|
2656
|
-
template_name, workspace_template_id, type_name
|
|
2657
|
-
)
|
|
3440
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
2658
3441
|
)
|
|
3442
|
+
return True
|
|
2659
3443
|
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
template_name, type_name
|
|
2665
|
-
)
|
|
2666
|
-
)
|
|
2667
|
-
continue
|
|
2668
|
-
|
|
2669
|
-
# end method definition
|
|
2670
|
-
|
|
2671
|
-
def processWorkspaces(self):
|
|
2672
|
-
"""Process workspaces in payload and create them in Extended ECM.
|
|
3444
|
+
# if this payload section has been processed successfully before we can return True
|
|
3445
|
+
# and skip processing once more
|
|
3446
|
+
if self.checkStatusFile(section_name):
|
|
3447
|
+
return True
|
|
2673
3448
|
|
|
2674
|
-
|
|
2675
|
-
Return: None
|
|
2676
|
-
Side Effects:
|
|
2677
|
-
Set workspace["nodeId] to the node ID of the created workspace
|
|
2678
|
-
"""
|
|
3449
|
+
success: bool = True
|
|
2679
3450
|
|
|
2680
3451
|
for workspace in self._workspaces:
|
|
2681
3452
|
# Read name from payload:
|
|
@@ -2708,14 +3479,10 @@ class Payload(object):
|
|
|
2708
3479
|
name, type_name
|
|
2709
3480
|
)
|
|
2710
3481
|
)
|
|
2711
|
-
|
|
2712
|
-
logger.debug(
|
|
2713
|
-
"Search for existing workspace delivered -> {}".format(response)
|
|
2714
|
-
)
|
|
2715
|
-
workspace_id = self._otcs.getResultValue(response, "id")
|
|
3482
|
+
workspace_id = self.determineWorkspaceID(workspace)
|
|
2716
3483
|
if workspace_id:
|
|
2717
3484
|
# we still want to set the nodeId as other parts of the payload depend on it:
|
|
2718
|
-
workspace["nodeId"] = workspace_id
|
|
3485
|
+
# workspace["nodeId"] = workspace_id
|
|
2719
3486
|
logger.info(
|
|
2720
3487
|
"Workspace -> {} of type -> {} does already exist and has ID -> {}! Skipping to next workspace...".format(
|
|
2721
3488
|
name, type_name, workspace_id
|
|
@@ -2752,13 +3519,13 @@ class Payload(object):
|
|
|
2752
3519
|
)
|
|
2753
3520
|
continue
|
|
2754
3521
|
|
|
2755
|
-
|
|
3522
|
+
parent_workspace_node_id = self.determineWorkspaceID(parent_workspace)
|
|
3523
|
+
if not parent_workspace_node_id:
|
|
2756
3524
|
logger.warning(
|
|
2757
3525
|
"Parent Workspace without node ID (parent workspace creation may have failed) - skipping to next workspace..."
|
|
2758
3526
|
)
|
|
2759
3527
|
continue
|
|
2760
|
-
|
|
2761
|
-
parent_workspace_node_id = parent_workspace["nodeId"]
|
|
3528
|
+
|
|
2762
3529
|
logger.info(
|
|
2763
3530
|
"Parent Workspace with logical ID -> {} has node ID -> {}".format(
|
|
2764
3531
|
parent_id, parent_workspace_node_id
|
|
@@ -3347,7 +4114,7 @@ class Payload(object):
|
|
|
3347
4114
|
workspace["nodeId"] = self._otcs.getResultValue(response, "id")
|
|
3348
4115
|
|
|
3349
4116
|
# We also get the name the workspace was finally created with.
|
|
3350
|
-
# This can be different form the
|
|
4117
|
+
# This can be different form the name in the payload as additional
|
|
3351
4118
|
# naming conventions from the Workspace Type definitions may apply.
|
|
3352
4119
|
# This is important to make the python container idem-potent.
|
|
3353
4120
|
response = self._otcs.getWorkspace(workspace["nodeId"])
|
|
@@ -3415,9 +4182,16 @@ class Payload(object):
|
|
|
3415
4182
|
)
|
|
3416
4183
|
)
|
|
3417
4184
|
|
|
4185
|
+
if success:
|
|
4186
|
+
self.writeStatusFile(section_name, self._workspaces)
|
|
4187
|
+
|
|
4188
|
+
return success
|
|
4189
|
+
|
|
3418
4190
|
# end method definition
|
|
3419
4191
|
|
|
3420
|
-
def processWorkspaceRelationships(
|
|
4192
|
+
def processWorkspaceRelationships(
|
|
4193
|
+
self, section_name: str = "workspaceRelationships"
|
|
4194
|
+
) -> bool:
|
|
3421
4195
|
"""Process workspaces relationships in payload and create them in Extended ECM.
|
|
3422
4196
|
|
|
3423
4197
|
Relationships can only be created if all workspaces have been created before.
|
|
@@ -3426,10 +4200,25 @@ class Payload(object):
|
|
|
3426
4200
|
Relationships are created between the node IDs of two business workspaces
|
|
3427
4201
|
(and not the logical IDs in the inital payload specification)
|
|
3428
4202
|
|
|
3429
|
-
Args:
|
|
3430
|
-
|
|
4203
|
+
Args:
|
|
4204
|
+
None
|
|
4205
|
+
Return:
|
|
4206
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
3431
4207
|
"""
|
|
3432
4208
|
|
|
4209
|
+
if not self._workspaces:
|
|
4210
|
+
logger.info(
|
|
4211
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
4212
|
+
)
|
|
4213
|
+
return True
|
|
4214
|
+
|
|
4215
|
+
# if this payload section has been processed successfully before we can return True
|
|
4216
|
+
# and skip processing once more
|
|
4217
|
+
if self.checkStatusFile(section_name):
|
|
4218
|
+
return True
|
|
4219
|
+
|
|
4220
|
+
success: bool = True
|
|
4221
|
+
|
|
3433
4222
|
for workspace in self._workspaces:
|
|
3434
4223
|
# Read name from payload:
|
|
3435
4224
|
if not "name" in workspace:
|
|
@@ -3464,13 +4253,13 @@ class Payload(object):
|
|
|
3464
4253
|
workspace_id = workspace["id"]
|
|
3465
4254
|
logger.info("Workspace -> {} has relationships - creating...".format(name))
|
|
3466
4255
|
|
|
3467
|
-
|
|
4256
|
+
workspace_node_id = self.determineWorkspaceID(workspace)
|
|
4257
|
+
if not workspace_node_id:
|
|
3468
4258
|
logger.warning(
|
|
3469
4259
|
"Workspace without node ID cannot have a relationship (workspace creation may have failed) - skipping to next workspace..."
|
|
3470
4260
|
)
|
|
3471
4261
|
continue
|
|
3472
4262
|
# now determine the actual node IDs of the workspaces (have been created above):
|
|
3473
|
-
workspace_node_id = workspace["nodeId"]
|
|
3474
4263
|
logger.info(
|
|
3475
4264
|
"Workspace with logical ID -> {} has node ID -> {}".format(
|
|
3476
4265
|
workspace_id, workspace_node_id
|
|
@@ -3493,6 +4282,7 @@ class Payload(object):
|
|
|
3493
4282
|
related_workspace_id
|
|
3494
4283
|
)
|
|
3495
4284
|
)
|
|
4285
|
+
success = False
|
|
3496
4286
|
continue
|
|
3497
4287
|
|
|
3498
4288
|
if "enabled" in related_workspace and not related_workspace["enabled"]:
|
|
@@ -3503,13 +4293,13 @@ class Payload(object):
|
|
|
3503
4293
|
)
|
|
3504
4294
|
continue
|
|
3505
4295
|
|
|
3506
|
-
|
|
4296
|
+
related_workspace_node_id = self.determineWorkspaceID(related_workspace)
|
|
4297
|
+
if not related_workspace_node_id:
|
|
3507
4298
|
logger.warning(
|
|
3508
4299
|
"Related Workspace without node ID (workspaces creation may have failed) - skipping to next workspace..."
|
|
3509
4300
|
)
|
|
3510
4301
|
continue
|
|
3511
|
-
|
|
3512
|
-
related_workspace_node_id = related_workspace["nodeId"]
|
|
4302
|
+
|
|
3513
4303
|
logger.info(
|
|
3514
4304
|
"Related Workspace with logical ID -> {} has node ID -> {}".format(
|
|
3515
4305
|
related_workspace_id, related_workspace_node_id
|
|
@@ -3539,20 +4329,41 @@ class Payload(object):
|
|
|
3539
4329
|
response = self._otcs.createWorkspaceRelationship(
|
|
3540
4330
|
workspace_node_id, related_workspace_node_id
|
|
3541
4331
|
)
|
|
3542
|
-
if response
|
|
4332
|
+
if not response:
|
|
3543
4333
|
logger.error("Failed to create workspace relationship.")
|
|
4334
|
+
success = False
|
|
3544
4335
|
else:
|
|
3545
4336
|
logger.info("Successfully created workspace relationship.")
|
|
3546
4337
|
|
|
4338
|
+
if success:
|
|
4339
|
+
self.writeStatusFile(section_name, self._workspaces)
|
|
4340
|
+
|
|
4341
|
+
return success
|
|
4342
|
+
|
|
3547
4343
|
# end method definition
|
|
3548
4344
|
|
|
3549
|
-
def processWorkspaceMembers(self):
|
|
4345
|
+
def processWorkspaceMembers(self, section_name: str = "workspaceMembers") -> bool:
|
|
3550
4346
|
"""Process workspaces members in payload and create them in Extended ECM.
|
|
3551
4347
|
|
|
3552
|
-
Args:
|
|
3553
|
-
|
|
4348
|
+
Args:
|
|
4349
|
+
None
|
|
4350
|
+
Return:
|
|
4351
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
3554
4352
|
"""
|
|
3555
4353
|
|
|
4354
|
+
if not self._workspaces:
|
|
4355
|
+
logger.info(
|
|
4356
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
4357
|
+
)
|
|
4358
|
+
return True
|
|
4359
|
+
|
|
4360
|
+
# if this payload section has been processed successfully before we can return True
|
|
4361
|
+
# and skip processing once more
|
|
4362
|
+
if self.checkStatusFile(section_name):
|
|
4363
|
+
return True
|
|
4364
|
+
|
|
4365
|
+
success: bool = True
|
|
4366
|
+
|
|
3556
4367
|
for workspace in self._workspaces:
|
|
3557
4368
|
# Read name from payload (just for logging):
|
|
3558
4369
|
if not "name" in workspace:
|
|
@@ -3586,14 +4397,14 @@ class Payload(object):
|
|
|
3586
4397
|
)
|
|
3587
4398
|
)
|
|
3588
4399
|
|
|
3589
|
-
|
|
4400
|
+
workspace_node_id = self.determineWorkspaceID(workspace)
|
|
4401
|
+
if not workspace_node_id:
|
|
3590
4402
|
logger.warning(
|
|
3591
4403
|
"Workspace without node ID cannot have a members (workspaces creation may have failed) - skipping to next workspace..."
|
|
3592
4404
|
)
|
|
3593
4405
|
continue
|
|
3594
4406
|
|
|
3595
4407
|
# now determine the actual node IDs of the workspaces (have been created by processWorkspaces()):
|
|
3596
|
-
workspace_node_id = workspace["nodeId"]
|
|
3597
4408
|
workspace_node = self._otcs.getNode(workspace_node_id)
|
|
3598
4409
|
workspace_owner_id = self._otcs.getResultValue(
|
|
3599
4410
|
workspace_node, "owner_user_id"
|
|
@@ -3656,6 +4467,7 @@ class Payload(object):
|
|
|
3656
4467
|
workspace_name
|
|
3657
4468
|
)
|
|
3658
4469
|
)
|
|
4470
|
+
success = False
|
|
3659
4471
|
continue
|
|
3660
4472
|
if (
|
|
3661
4473
|
member_users == [] and member_groups == []
|
|
@@ -3677,6 +4489,7 @@ class Payload(object):
|
|
|
3677
4489
|
workspace_name, member_role_name
|
|
3678
4490
|
)
|
|
3679
4491
|
)
|
|
4492
|
+
success = False
|
|
3680
4493
|
continue
|
|
3681
4494
|
logger.info("Role -> {} has ID -> {}".format(member_role_name, role_id))
|
|
3682
4495
|
|
|
@@ -3726,6 +4539,7 @@ class Payload(object):
|
|
|
3726
4539
|
workspace_name,
|
|
3727
4540
|
)
|
|
3728
4541
|
)
|
|
4542
|
+
success = False
|
|
3729
4543
|
else:
|
|
3730
4544
|
logger.info(
|
|
3731
4545
|
"Successfully added user -> {} ({}) to role -> {} of workspace -> {}".format(
|
|
@@ -3746,6 +4560,7 @@ class Payload(object):
|
|
|
3746
4560
|
logger.error(
|
|
3747
4561
|
"Cannot find group with name -> {}".format(member_group)
|
|
3748
4562
|
)
|
|
4563
|
+
success = False
|
|
3749
4564
|
continue
|
|
3750
4565
|
group_id = member_group_id["id"]
|
|
3751
4566
|
|
|
@@ -3761,6 +4576,7 @@ class Payload(object):
|
|
|
3761
4576
|
workspace_name,
|
|
3762
4577
|
)
|
|
3763
4578
|
)
|
|
4579
|
+
success = False
|
|
3764
4580
|
else:
|
|
3765
4581
|
logger.info(
|
|
3766
4582
|
"Successfully added group -> {} ({}) to role -> {} of workspace -> {}".format(
|
|
@@ -3771,17 +4587,38 @@ class Payload(object):
|
|
|
3771
4587
|
)
|
|
3772
4588
|
)
|
|
3773
4589
|
|
|
4590
|
+
if success:
|
|
4591
|
+
self.writeStatusFile(section_name, self._workspaces)
|
|
4592
|
+
|
|
4593
|
+
return success
|
|
4594
|
+
|
|
3774
4595
|
# end method definition
|
|
3775
4596
|
|
|
3776
|
-
def processWebReports(
|
|
4597
|
+
def processWebReports(
|
|
4598
|
+
self, web_reports: list, section_name: str = "webReports"
|
|
4599
|
+
) -> bool:
|
|
3777
4600
|
"""Process web reports in payload and run them in Extended ECM.
|
|
3778
4601
|
|
|
3779
4602
|
Args:
|
|
3780
4603
|
web_reports (list): list of web reports. As we have two different list (pre and post)
|
|
3781
4604
|
we need to pass the actual list as parameter.
|
|
3782
|
-
Return:
|
|
4605
|
+
Return:
|
|
4606
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
3783
4607
|
"""
|
|
3784
4608
|
|
|
4609
|
+
if not web_reports:
|
|
4610
|
+
logger.info(
|
|
4611
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
4612
|
+
)
|
|
4613
|
+
return True
|
|
4614
|
+
|
|
4615
|
+
# if this payload section has been processed successfully before we can return True
|
|
4616
|
+
# and skip processing once more
|
|
4617
|
+
if self.checkStatusFile(section_name):
|
|
4618
|
+
return True
|
|
4619
|
+
|
|
4620
|
+
success: bool = True
|
|
4621
|
+
|
|
3785
4622
|
for web_report in web_reports:
|
|
3786
4623
|
nick_name = web_report["nickname"]
|
|
3787
4624
|
|
|
@@ -3803,6 +4640,7 @@ class Payload(object):
|
|
|
3803
4640
|
nick_name
|
|
3804
4641
|
)
|
|
3805
4642
|
)
|
|
4643
|
+
success = False
|
|
3806
4644
|
continue
|
|
3807
4645
|
|
|
3808
4646
|
# be careful to avoid key errors as Web Report parameters are optional:
|
|
@@ -3824,6 +4662,7 @@ class Payload(object):
|
|
|
3824
4662
|
nick_name
|
|
3825
4663
|
)
|
|
3826
4664
|
)
|
|
4665
|
+
success = False
|
|
3827
4666
|
continue
|
|
3828
4667
|
lets_continue = False
|
|
3829
4668
|
# Check 2: Iterate through the actual parameters given in the payload
|
|
@@ -3840,6 +4679,7 @@ class Payload(object):
|
|
|
3840
4679
|
nick_name, key
|
|
3841
4680
|
)
|
|
3842
4681
|
)
|
|
4682
|
+
success = False
|
|
3843
4683
|
lets_continue = True # we cannot do a "continue" here directly as we are in an inner loop
|
|
3844
4684
|
# Check 3: Iterate through the formal parameters and validate there's a matching
|
|
3845
4685
|
# actual parameter defined in the payload for each mandatory formal parameter
|
|
@@ -3855,6 +4695,7 @@ class Payload(object):
|
|
|
3855
4695
|
nick_name, formal_param["parm_name"]
|
|
3856
4696
|
)
|
|
3857
4697
|
)
|
|
4698
|
+
success = False
|
|
3858
4699
|
lets_continue = True # we cannot do a "continue" here directly as we are in an inner loop
|
|
3859
4700
|
# Did any of the checks fail?
|
|
3860
4701
|
if lets_continue:
|
|
@@ -3885,6 +4726,7 @@ class Payload(object):
|
|
|
3885
4726
|
nick_name, required_param["parm_name"]
|
|
3886
4727
|
)
|
|
3887
4728
|
)
|
|
4729
|
+
success = False
|
|
3888
4730
|
continue
|
|
3889
4731
|
else: # we are good to proceed!
|
|
3890
4732
|
logger.debug(
|
|
@@ -3895,19 +4737,41 @@ class Payload(object):
|
|
|
3895
4737
|
response = self._otcs.runWebReport(nick_name)
|
|
3896
4738
|
if response == None:
|
|
3897
4739
|
logger.error("Failed to run web report -> {}".format(nick_name))
|
|
4740
|
+
success = False
|
|
4741
|
+
|
|
4742
|
+
if success:
|
|
4743
|
+
self.writeStatusFile(section_name, web_reports)
|
|
4744
|
+
|
|
4745
|
+
return success
|
|
3898
4746
|
|
|
3899
4747
|
# end method definition
|
|
3900
4748
|
|
|
3901
|
-
def processCSApplications(
|
|
4749
|
+
def processCSApplications(
|
|
4750
|
+
self, otcs_object: object = None, section_name: str = "csApplications"
|
|
4751
|
+
) -> bool:
|
|
3902
4752
|
"""Process CS applications in payload and install them in Extended ECM.
|
|
3903
4753
|
The CS Applications need to be installed in all frontend and backends.
|
|
3904
4754
|
|
|
3905
4755
|
Args:
|
|
3906
4756
|
otcs_object (object): this can either be the OTCS frontend or OTCS backend. If None
|
|
3907
4757
|
then the otcs_backend is used.
|
|
3908
|
-
Return:
|
|
4758
|
+
Return:
|
|
4759
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
3909
4760
|
"""
|
|
3910
4761
|
|
|
4762
|
+
if not self._cs_applications:
|
|
4763
|
+
logger.info(
|
|
4764
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
4765
|
+
)
|
|
4766
|
+
return True
|
|
4767
|
+
|
|
4768
|
+
# if this payload section has been processed successfully before we can return True
|
|
4769
|
+
# and skip processing once more
|
|
4770
|
+
if self.checkStatusFile(section_name):
|
|
4771
|
+
return True
|
|
4772
|
+
|
|
4773
|
+
success: bool = True
|
|
4774
|
+
|
|
3911
4775
|
# OTCS backend is the default:
|
|
3912
4776
|
if not otcs_object:
|
|
3913
4777
|
otcs_object = self._otcs_backend
|
|
@@ -3937,19 +4801,38 @@ class Payload(object):
|
|
|
3937
4801
|
logger.error(
|
|
3938
4802
|
"Failed to install CS Application -> {}!".format(application_name)
|
|
3939
4803
|
)
|
|
4804
|
+
success = False
|
|
4805
|
+
|
|
4806
|
+
if success:
|
|
4807
|
+
self.writeStatusFile(section_name, self._cs_applications)
|
|
4808
|
+
|
|
4809
|
+
return success
|
|
3940
4810
|
|
|
3941
4811
|
# end method definition
|
|
3942
4812
|
|
|
3943
|
-
def processUserSettings(self):
|
|
4813
|
+
def processUserSettings(self, section_name: str = "userSettings") -> bool:
|
|
3944
4814
|
"""Process user settings in payload and apply themin OTDS.
|
|
3945
4815
|
This includes password settings and user display settings.
|
|
3946
4816
|
|
|
3947
4817
|
Args:
|
|
3948
4818
|
None
|
|
3949
4819
|
Returns:
|
|
3950
|
-
|
|
4820
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
3951
4821
|
"""
|
|
3952
4822
|
|
|
4823
|
+
if not self._users:
|
|
4824
|
+
logger.info(
|
|
4825
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
4826
|
+
)
|
|
4827
|
+
return True
|
|
4828
|
+
|
|
4829
|
+
# if this payload section has been processed successfully before we can return True
|
|
4830
|
+
# and skip processing once more
|
|
4831
|
+
if self.checkStatusFile(section_name):
|
|
4832
|
+
return True
|
|
4833
|
+
|
|
4834
|
+
success: bool = True
|
|
4835
|
+
|
|
3953
4836
|
for user in self._users:
|
|
3954
4837
|
user_name = user["name"]
|
|
3955
4838
|
|
|
@@ -3964,6 +4847,8 @@ class Payload(object):
|
|
|
3964
4847
|
user_partition = self._otcs.config()["partition"]
|
|
3965
4848
|
if not user_partition:
|
|
3966
4849
|
logger.error("User partition not found!")
|
|
4850
|
+
success = False
|
|
4851
|
+
continue
|
|
3967
4852
|
|
|
3968
4853
|
# Set the OTDS display name. Extended ECM does not use this but
|
|
3969
4854
|
# it makes AppWorks display users correctly (and it doesn't hurt)
|
|
@@ -3985,6 +4870,7 @@ class Payload(object):
|
|
|
3985
4870
|
user_name, user_display_name
|
|
3986
4871
|
)
|
|
3987
4872
|
)
|
|
4873
|
+
success = False
|
|
3988
4874
|
|
|
3989
4875
|
# Don't enforce the user to reset password at first login (settings in OTDS):
|
|
3990
4876
|
logger.info(
|
|
@@ -3993,10 +4879,19 @@ class Payload(object):
|
|
|
3993
4879
|
response = self._otds.updateUser(
|
|
3994
4880
|
user_partition, user_name, "UserMustChangePasswordAtNextSignIn", "False"
|
|
3995
4881
|
)
|
|
4882
|
+
if not response:
|
|
4883
|
+
success = False
|
|
4884
|
+
|
|
4885
|
+
if success:
|
|
4886
|
+
self.writeStatusFile(section_name, self._users)
|
|
4887
|
+
|
|
4888
|
+
return success
|
|
3996
4889
|
|
|
3997
4890
|
# end method definition
|
|
3998
4891
|
|
|
3999
|
-
def processUserFavoritesAndProfiles(
|
|
4892
|
+
def processUserFavoritesAndProfiles(
|
|
4893
|
+
self, section_name: str = "userFavoritesAndProfiles"
|
|
4894
|
+
) -> bool:
|
|
4000
4895
|
"""Process user favorites in payload and create them in Extended ECM.
|
|
4001
4896
|
This method also simulates browsing the favorites to populate the
|
|
4002
4897
|
widgets on the landing pages and sets personal preferences.
|
|
@@ -4004,9 +4899,22 @@ class Payload(object):
|
|
|
4004
4899
|
Args:
|
|
4005
4900
|
None
|
|
4006
4901
|
Returns:
|
|
4007
|
-
|
|
4902
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
4008
4903
|
"""
|
|
4009
4904
|
|
|
4905
|
+
if not self._users:
|
|
4906
|
+
logger.info(
|
|
4907
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
4908
|
+
)
|
|
4909
|
+
return True
|
|
4910
|
+
|
|
4911
|
+
# if this payload section has been processed successfully before we can return True
|
|
4912
|
+
# and skip processing once more
|
|
4913
|
+
if self.checkStatusFile(section_name):
|
|
4914
|
+
return True
|
|
4915
|
+
|
|
4916
|
+
success: bool = True
|
|
4917
|
+
|
|
4010
4918
|
# We can only set favorites if we impersonate / authenticate as the user.
|
|
4011
4919
|
# The following code (for loop) will change the authenticated user - we need to
|
|
4012
4920
|
# switch it back to admin user later so we safe the admin credentials for this:
|
|
@@ -4037,6 +4945,7 @@ class Payload(object):
|
|
|
4037
4945
|
cookie = self._otcs.authenticate(True)
|
|
4038
4946
|
if not cookie:
|
|
4039
4947
|
logger.error("Couldn't authenticate user -> {}".format(user_name))
|
|
4948
|
+
success = False
|
|
4040
4949
|
continue
|
|
4041
4950
|
|
|
4042
4951
|
# we update the user profile to activate navigation tree:
|
|
@@ -4060,34 +4969,21 @@ class Payload(object):
|
|
|
4060
4969
|
(item for item in self._workspaces if item["id"] == favorite), None
|
|
4061
4970
|
)
|
|
4062
4971
|
is_workspace = False
|
|
4063
|
-
if favorite_item
|
|
4972
|
+
if favorite_item:
|
|
4064
4973
|
logger.info(
|
|
4065
4974
|
"Found favorite item (workspace) in payload -> {}".format(
|
|
4066
4975
|
favorite_item["name"]
|
|
4067
4976
|
)
|
|
4068
4977
|
)
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
favorite
|
|
4978
|
+
favorite_id = self.determineWorkspaceID(favorite_item)
|
|
4979
|
+
if not favorite_id:
|
|
4980
|
+
logger.warning(
|
|
4981
|
+
"Workspace of type -> {} and name -> {} does not exist. Cannot create favorite. Skipping...".format(
|
|
4982
|
+
favorite_item["type_name"], favorite_item["name"]
|
|
4075
4983
|
)
|
|
4076
4984
|
)
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
)
|
|
4080
|
-
favorite_id = self._otcs.getResultValue(response, "id")
|
|
4081
|
-
if not favorite_id:
|
|
4082
|
-
logger.warning(
|
|
4083
|
-
"Workspace of type -> {} and name -> {} does not exist. Cannot create favorite. Skipping...".format(
|
|
4084
|
-
favorite_item["type_name"], favorite_item["name"]
|
|
4085
|
-
)
|
|
4086
|
-
)
|
|
4087
|
-
continue
|
|
4088
|
-
else:
|
|
4089
|
-
# store ID in payload in case it is used again
|
|
4090
|
-
favorite_item["nodeId"] = favorite_id
|
|
4985
|
+
continue
|
|
4986
|
+
|
|
4091
4987
|
is_workspace = True
|
|
4092
4988
|
else:
|
|
4093
4989
|
# alternatively try to find the item as a nickname:
|
|
@@ -4141,6 +5037,7 @@ class Payload(object):
|
|
|
4141
5037
|
proxy, user_name
|
|
4142
5038
|
)
|
|
4143
5039
|
)
|
|
5040
|
+
success = False
|
|
4144
5041
|
continue
|
|
4145
5042
|
proxy_user_id = proxy_user["id"]
|
|
4146
5043
|
|
|
@@ -4175,15 +5072,37 @@ class Payload(object):
|
|
|
4175
5072
|
# True = force new login with new user
|
|
4176
5073
|
cookie = self._otcs.authenticate(True)
|
|
4177
5074
|
|
|
5075
|
+
if success:
|
|
5076
|
+
self.writeStatusFile(section_name, self._users)
|
|
5077
|
+
|
|
5078
|
+
return success
|
|
5079
|
+
|
|
4178
5080
|
# end method definition
|
|
4179
5081
|
|
|
4180
|
-
def processSecurityClearances(
|
|
5082
|
+
def processSecurityClearances(
|
|
5083
|
+
self, section_name: str = "securityClearances"
|
|
5084
|
+
) -> bool:
|
|
4181
5085
|
"""Process Security Clearances for Extended ECM.
|
|
4182
5086
|
|
|
4183
|
-
Args:
|
|
4184
|
-
|
|
5087
|
+
Args:
|
|
5088
|
+
None
|
|
5089
|
+
Return:
|
|
5090
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
4185
5091
|
"""
|
|
4186
5092
|
|
|
5093
|
+
if not self._security_clearances:
|
|
5094
|
+
logger.info(
|
|
5095
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
5096
|
+
)
|
|
5097
|
+
return True
|
|
5098
|
+
|
|
5099
|
+
# if this payload section has been processed successfully before we can return True
|
|
5100
|
+
# and skip processing once more
|
|
5101
|
+
if self.checkStatusFile(section_name):
|
|
5102
|
+
return True
|
|
5103
|
+
|
|
5104
|
+
success: bool = True
|
|
5105
|
+
|
|
4187
5106
|
for security_clearance in self._security_clearances:
|
|
4188
5107
|
clearance_level = security_clearance.get("level")
|
|
4189
5108
|
clearance_name = security_clearance.get("name")
|
|
@@ -4212,16 +5131,39 @@ class Payload(object):
|
|
|
4212
5131
|
logger.error(
|
|
4213
5132
|
"Cannot create Security Clearance - either level or name is missing!"
|
|
4214
5133
|
)
|
|
5134
|
+
success = False
|
|
5135
|
+
|
|
5136
|
+
if success:
|
|
5137
|
+
self.writeStatusFile(section_name, self._security_clearances)
|
|
5138
|
+
|
|
5139
|
+
return success
|
|
4215
5140
|
|
|
4216
5141
|
# end method definition
|
|
4217
5142
|
|
|
4218
|
-
def processSupplementalMarkings(
|
|
5143
|
+
def processSupplementalMarkings(
|
|
5144
|
+
self, section_name: str = "supplementalMarkings"
|
|
5145
|
+
) -> bool:
|
|
4219
5146
|
"""Process Supplemental Markings for Extended ECM.
|
|
4220
5147
|
|
|
4221
|
-
Args:
|
|
4222
|
-
|
|
5148
|
+
Args:
|
|
5149
|
+
None
|
|
5150
|
+
Return:
|
|
5151
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
4223
5152
|
"""
|
|
4224
5153
|
|
|
5154
|
+
if not self._supplemental_markings:
|
|
5155
|
+
logger.info(
|
|
5156
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
5157
|
+
)
|
|
5158
|
+
return True
|
|
5159
|
+
|
|
5160
|
+
# if this payload section has been processed successfully before we can return True
|
|
5161
|
+
# and skip processing once more
|
|
5162
|
+
if self.checkStatusFile(section_name):
|
|
5163
|
+
return True
|
|
5164
|
+
|
|
5165
|
+
success: bool = True
|
|
5166
|
+
|
|
4225
5167
|
for supplemental_marking in self._supplemental_markings:
|
|
4226
5168
|
code = supplemental_marking.get("code")
|
|
4227
5169
|
|
|
@@ -4250,16 +5192,37 @@ class Payload(object):
|
|
|
4250
5192
|
logger.error(
|
|
4251
5193
|
"Cannot create Supplemental Marking - either code or description is missing!"
|
|
4252
5194
|
)
|
|
5195
|
+
success = False
|
|
5196
|
+
|
|
5197
|
+
if success:
|
|
5198
|
+
self.writeStatusFile(section_name, self._supplemental_markings)
|
|
5199
|
+
|
|
5200
|
+
return success
|
|
4253
5201
|
|
|
4254
5202
|
# end method definition
|
|
4255
5203
|
|
|
4256
|
-
def processUserSecurity(self):
|
|
5204
|
+
def processUserSecurity(self, section_name: str = "userSecurity"):
|
|
4257
5205
|
"""Process Security Clearance and Supplemental Markings for Extended ECM users.
|
|
4258
5206
|
|
|
4259
|
-
Args:
|
|
4260
|
-
|
|
5207
|
+
Args:
|
|
5208
|
+
None
|
|
5209
|
+
Return:
|
|
5210
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
4261
5211
|
"""
|
|
4262
5212
|
|
|
5213
|
+
if not self._users:
|
|
5214
|
+
logger.info(
|
|
5215
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
5216
|
+
)
|
|
5217
|
+
return True
|
|
5218
|
+
|
|
5219
|
+
# if this payload section has been processed successfully before we can return True
|
|
5220
|
+
# and skip processing once more
|
|
5221
|
+
if self.checkStatusFile(section_name):
|
|
5222
|
+
return True
|
|
5223
|
+
|
|
5224
|
+
success: bool = True
|
|
5225
|
+
|
|
4263
5226
|
for user in self._users:
|
|
4264
5227
|
user_id = user.get("id")
|
|
4265
5228
|
user_name = user.get("name")
|
|
@@ -4286,9 +5249,16 @@ class Payload(object):
|
|
|
4286
5249
|
user_id, user_supplemental_markings
|
|
4287
5250
|
)
|
|
4288
5251
|
|
|
5252
|
+
if success:
|
|
5253
|
+
self.writeStatusFile(section_name, self._users)
|
|
5254
|
+
|
|
5255
|
+
return success
|
|
5256
|
+
|
|
4289
5257
|
# end method definition
|
|
4290
5258
|
|
|
4291
|
-
def processRecordsManagementSettings(
|
|
5259
|
+
def processRecordsManagementSettings(
|
|
5260
|
+
self, section_name: str = "recordsManagementSettings"
|
|
5261
|
+
):
|
|
4292
5262
|
"""Process Records Management Settings for Extended ECM.
|
|
4293
5263
|
The setting files need to be placed in the OTCS file system file via
|
|
4294
5264
|
a transport into the Support Asset Volume.
|
|
@@ -4297,6 +5267,19 @@ class Payload(object):
|
|
|
4297
5267
|
Return: None
|
|
4298
5268
|
"""
|
|
4299
5269
|
|
|
5270
|
+
if not self._records_management_settings:
|
|
5271
|
+
logger.info(
|
|
5272
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
5273
|
+
)
|
|
5274
|
+
return True
|
|
5275
|
+
|
|
5276
|
+
# if this payload section has been processed successfully before we can return True
|
|
5277
|
+
# and skip processing once more
|
|
5278
|
+
if self.checkStatusFile(section_name):
|
|
5279
|
+
return True
|
|
5280
|
+
|
|
5281
|
+
success: bool = True
|
|
5282
|
+
|
|
4300
5283
|
if (
|
|
4301
5284
|
"records_management_system_settings" in self._records_management_settings
|
|
4302
5285
|
and self._records_management_settings["records_management_system_settings"]
|
|
@@ -4308,7 +5291,9 @@ class Payload(object):
|
|
|
4308
5291
|
"records_management_system_settings"
|
|
4309
5292
|
]
|
|
4310
5293
|
)
|
|
4311
|
-
self._otcs.importRecordsManagementSettings(filename)
|
|
5294
|
+
response = self._otcs.importRecordsManagementSettings(filename)
|
|
5295
|
+
if not response:
|
|
5296
|
+
success = False
|
|
4312
5297
|
|
|
4313
5298
|
if (
|
|
4314
5299
|
"records_management_codes" in self._records_management_settings
|
|
@@ -4318,7 +5303,9 @@ class Payload(object):
|
|
|
4318
5303
|
self._custom_settings_dir
|
|
4319
5304
|
+ self._records_management_settings["records_management_codes"]
|
|
4320
5305
|
)
|
|
4321
|
-
self._otcs.importRecordsManagementCodes(filename)
|
|
5306
|
+
response = self._otcs.importRecordsManagementCodes(filename)
|
|
5307
|
+
if not response:
|
|
5308
|
+
success = False
|
|
4322
5309
|
|
|
4323
5310
|
if (
|
|
4324
5311
|
"records_management_rsis" in self._records_management_settings
|
|
@@ -4328,7 +5315,9 @@ class Payload(object):
|
|
|
4328
5315
|
self._custom_settings_dir
|
|
4329
5316
|
+ self._records_management_settings["records_management_rsis"]
|
|
4330
5317
|
)
|
|
4331
|
-
self._otcs.importRecordsManagementRSIs(filename)
|
|
5318
|
+
response = self._otcs.importRecordsManagementRSIs(filename)
|
|
5319
|
+
if not response:
|
|
5320
|
+
success = False
|
|
4332
5321
|
|
|
4333
5322
|
if (
|
|
4334
5323
|
"physical_objects_system_settings" in self._records_management_settings
|
|
@@ -4339,7 +5328,9 @@ class Payload(object):
|
|
|
4339
5328
|
self._custom_settings_dir
|
|
4340
5329
|
+ self._records_management_settings["physical_objects_system_settings"]
|
|
4341
5330
|
)
|
|
4342
|
-
self._otcs.importPhysicalObjectsSettings(filename)
|
|
5331
|
+
response = self._otcs.importPhysicalObjectsSettings(filename)
|
|
5332
|
+
if not response:
|
|
5333
|
+
success = False
|
|
4343
5334
|
|
|
4344
5335
|
if (
|
|
4345
5336
|
"physical_objects_codes" in self._records_management_settings
|
|
@@ -4349,7 +5340,9 @@ class Payload(object):
|
|
|
4349
5340
|
self._custom_settings_dir
|
|
4350
5341
|
+ self._records_management_settings["physical_objects_codes"]
|
|
4351
5342
|
)
|
|
4352
|
-
self._otcs.importPhysicalObjectsCodes(filename)
|
|
5343
|
+
response = self._otcs.importPhysicalObjectsCodes(filename)
|
|
5344
|
+
if not response:
|
|
5345
|
+
success = False
|
|
4353
5346
|
|
|
4354
5347
|
if (
|
|
4355
5348
|
"physical_objects_locators" in self._records_management_settings
|
|
@@ -4359,7 +5352,9 @@ class Payload(object):
|
|
|
4359
5352
|
self._custom_settings_dir
|
|
4360
5353
|
+ self._records_management_settings["physical_objects_locators"]
|
|
4361
5354
|
)
|
|
4362
|
-
self._otcs.importPhysicalObjectsLocators(filename)
|
|
5355
|
+
response = self._otcs.importPhysicalObjectsLocators(filename)
|
|
5356
|
+
if not response:
|
|
5357
|
+
success = False
|
|
4363
5358
|
|
|
4364
5359
|
if (
|
|
4365
5360
|
"security_clearance_codes" in self._records_management_settings
|
|
@@ -4369,17 +5364,39 @@ class Payload(object):
|
|
|
4369
5364
|
self._custom_settings_dir
|
|
4370
5365
|
+ self._records_management_settings["security_clearance_codes"]
|
|
4371
5366
|
)
|
|
4372
|
-
self._otcs.importSecurityClearanceCodes(filename)
|
|
5367
|
+
response = self._otcs.importSecurityClearanceCodes(filename)
|
|
5368
|
+
if not response:
|
|
5369
|
+
success = False
|
|
5370
|
+
|
|
5371
|
+
if success:
|
|
5372
|
+
self.writeStatusFile(section_name, self._records_management_settings)
|
|
5373
|
+
|
|
5374
|
+
return success
|
|
4373
5375
|
|
|
4374
5376
|
# end method definition
|
|
4375
5377
|
|
|
4376
|
-
def processHolds(self):
|
|
5378
|
+
def processHolds(self, section_name: str = "holds") -> bool:
|
|
4377
5379
|
"""Process Records Management Holds for Extended ECM users.
|
|
4378
5380
|
|
|
4379
|
-
Args:
|
|
4380
|
-
|
|
5381
|
+
Args:
|
|
5382
|
+
None
|
|
5383
|
+
Return:
|
|
5384
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
4381
5385
|
"""
|
|
4382
5386
|
|
|
5387
|
+
if not self._holds:
|
|
5388
|
+
logger.info(
|
|
5389
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
5390
|
+
)
|
|
5391
|
+
return True
|
|
5392
|
+
|
|
5393
|
+
# if this payload section has been processed successfully before we can return True
|
|
5394
|
+
# and skip processing once more
|
|
5395
|
+
if self.checkStatusFile(section_name):
|
|
5396
|
+
return True
|
|
5397
|
+
|
|
5398
|
+
success: bool = True
|
|
5399
|
+
|
|
4383
5400
|
for hold in self._holds:
|
|
4384
5401
|
if not "name" in hold:
|
|
4385
5402
|
logger.error("Cannot create Hold without a name! Skipping...")
|
|
@@ -4392,6 +5409,7 @@ class Payload(object):
|
|
|
4392
5409
|
hold_name
|
|
4393
5410
|
)
|
|
4394
5411
|
)
|
|
5412
|
+
success = False
|
|
4395
5413
|
continue
|
|
4396
5414
|
hold_type = hold["type"]
|
|
4397
5415
|
|
|
@@ -4451,16 +5469,40 @@ class Payload(object):
|
|
|
4451
5469
|
hold_name, hold["holdID"]
|
|
4452
5470
|
)
|
|
4453
5471
|
)
|
|
5472
|
+
else:
|
|
5473
|
+
success = False
|
|
5474
|
+
|
|
5475
|
+
if success:
|
|
5476
|
+
self.writeStatusFile(section_name, self._holds)
|
|
5477
|
+
|
|
5478
|
+
return success
|
|
4454
5479
|
|
|
4455
5480
|
# end method definition
|
|
4456
5481
|
|
|
4457
|
-
def processAdditionalGroupMembers(
|
|
5482
|
+
def processAdditionalGroupMembers(
|
|
5483
|
+
self, section_name: str = "additionalGroupMemberships"
|
|
5484
|
+
) -> bool:
|
|
4458
5485
|
"""Process additional groups memberships we want to have in OTDS.
|
|
4459
5486
|
|
|
4460
|
-
Args:
|
|
4461
|
-
|
|
5487
|
+
Args:
|
|
5488
|
+
None
|
|
5489
|
+
Return:
|
|
5490
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
4462
5491
|
"""
|
|
4463
5492
|
|
|
5493
|
+
if not self._additional_group_members:
|
|
5494
|
+
logger.info(
|
|
5495
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
5496
|
+
)
|
|
5497
|
+
return True
|
|
5498
|
+
|
|
5499
|
+
# if this payload section has been processed successfully before we can return True
|
|
5500
|
+
# and skip processing once more
|
|
5501
|
+
if self.checkStatusFile(section_name):
|
|
5502
|
+
return True
|
|
5503
|
+
|
|
5504
|
+
success: bool = True
|
|
5505
|
+
|
|
4464
5506
|
for additional_group_member in self._additional_group_members:
|
|
4465
5507
|
if not "parent_group" in additional_group_member:
|
|
4466
5508
|
logger.error("Missing parent_group! Skipping...")
|
|
@@ -4484,6 +5526,7 @@ class Payload(object):
|
|
|
4484
5526
|
logger.error(
|
|
4485
5527
|
"Either group_name or user_name need to be specified! Skipping..."
|
|
4486
5528
|
)
|
|
5529
|
+
success = False
|
|
4487
5530
|
continue
|
|
4488
5531
|
if "group_name" in additional_group_member:
|
|
4489
5532
|
group_name = additional_group_member["group_name"]
|
|
@@ -4499,6 +5542,7 @@ class Payload(object):
|
|
|
4499
5542
|
group_name, parent_group
|
|
4500
5543
|
)
|
|
4501
5544
|
)
|
|
5545
|
+
success = False
|
|
4502
5546
|
elif "user_name" in additional_group_member:
|
|
4503
5547
|
user_name = additional_group_member["user_name"]
|
|
4504
5548
|
logger.info(
|
|
@@ -4513,16 +5557,39 @@ class Payload(object):
|
|
|
4513
5557
|
user_name, parent_group
|
|
4514
5558
|
)
|
|
4515
5559
|
)
|
|
5560
|
+
success = False
|
|
5561
|
+
|
|
5562
|
+
if success:
|
|
5563
|
+
self.writeStatusFile(section_name, self._additional_group_members)
|
|
5564
|
+
|
|
5565
|
+
return success
|
|
4516
5566
|
|
|
4517
5567
|
# end method definition
|
|
4518
5568
|
|
|
4519
|
-
def processAdditionalAccessRoleMembers(
|
|
5569
|
+
def processAdditionalAccessRoleMembers(
|
|
5570
|
+
self, section_name: str = "additionalAccessRoleMemberships"
|
|
5571
|
+
) -> bool:
|
|
4520
5572
|
"""Process additional access role memberships we want to have in OTDS.
|
|
4521
5573
|
|
|
4522
|
-
Args:
|
|
4523
|
-
|
|
5574
|
+
Args:
|
|
5575
|
+
None
|
|
5576
|
+
Return:
|
|
5577
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
4524
5578
|
"""
|
|
4525
5579
|
|
|
5580
|
+
if not self._additional_access_role_members:
|
|
5581
|
+
logger.info(
|
|
5582
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
5583
|
+
)
|
|
5584
|
+
return True
|
|
5585
|
+
|
|
5586
|
+
# if this payload section has been processed successfully before we can return True
|
|
5587
|
+
# and skip processing once more
|
|
5588
|
+
if self.checkStatusFile(section_name):
|
|
5589
|
+
return True
|
|
5590
|
+
|
|
5591
|
+
success: bool = True
|
|
5592
|
+
|
|
4526
5593
|
for additional_access_role_member in self._additional_access_role_members:
|
|
4527
5594
|
if not "access_role" in additional_access_role_member:
|
|
4528
5595
|
logger.error("Missing access_role! Skipping...")
|
|
@@ -4548,6 +5615,7 @@ class Payload(object):
|
|
|
4548
5615
|
logger.error(
|
|
4549
5616
|
"Either group_name or user_name need to be specified! Skipping..."
|
|
4550
5617
|
)
|
|
5618
|
+
success = False
|
|
4551
5619
|
continue
|
|
4552
5620
|
if "group_name" in additional_access_role_member:
|
|
4553
5621
|
group_name = additional_access_role_member["group_name"]
|
|
@@ -4563,6 +5631,7 @@ class Payload(object):
|
|
|
4563
5631
|
group_name, access_role
|
|
4564
5632
|
)
|
|
4565
5633
|
)
|
|
5634
|
+
success = False
|
|
4566
5635
|
elif "user_name" in additional_access_role_member:
|
|
4567
5636
|
user_name = additional_access_role_member["user_name"]
|
|
4568
5637
|
logger.info(
|
|
@@ -4577,6 +5646,7 @@ class Payload(object):
|
|
|
4577
5646
|
user_name, access_role
|
|
4578
5647
|
)
|
|
4579
5648
|
)
|
|
5649
|
+
success = False
|
|
4580
5650
|
elif "partition_name" in additional_access_role_member:
|
|
4581
5651
|
partition_name = additional_access_role_member["partition_name"]
|
|
4582
5652
|
logger.info(
|
|
@@ -4593,16 +5663,37 @@ class Payload(object):
|
|
|
4593
5663
|
partition_name, access_role
|
|
4594
5664
|
)
|
|
4595
5665
|
)
|
|
5666
|
+
success = False
|
|
5667
|
+
|
|
5668
|
+
if success:
|
|
5669
|
+
self.writeStatusFile(section_name, self._additional_access_role_members)
|
|
5670
|
+
|
|
5671
|
+
return success
|
|
4596
5672
|
|
|
4597
5673
|
# end method definition
|
|
4598
5674
|
|
|
4599
|
-
def processRenamings(self):
|
|
5675
|
+
def processRenamings(self, section_name: str = "renamings") -> bool:
|
|
4600
5676
|
"""Process renamings specified in payload and rename existing Extended ECM items.
|
|
4601
5677
|
|
|
4602
|
-
Args:
|
|
4603
|
-
|
|
5678
|
+
Args:
|
|
5679
|
+
None
|
|
5680
|
+
Return:
|
|
5681
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
4604
5682
|
"""
|
|
4605
5683
|
|
|
5684
|
+
if not self._renamings:
|
|
5685
|
+
logger.info(
|
|
5686
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
5687
|
+
)
|
|
5688
|
+
return True
|
|
5689
|
+
|
|
5690
|
+
# if this payload section has been processed successfully before we can return True
|
|
5691
|
+
# and skip processing once more
|
|
5692
|
+
if self.checkStatusFile(section_name):
|
|
5693
|
+
return True
|
|
5694
|
+
|
|
5695
|
+
success: bool = True
|
|
5696
|
+
|
|
4606
5697
|
for renaming in self._renamings:
|
|
4607
5698
|
if not "nodeid" in renaming:
|
|
4608
5699
|
if not "volume" in renaming:
|
|
@@ -4631,19 +5722,38 @@ class Payload(object):
|
|
|
4631
5722
|
node_id, renaming["name"]
|
|
4632
5723
|
)
|
|
4633
5724
|
)
|
|
5725
|
+
success = False
|
|
5726
|
+
|
|
5727
|
+
if success:
|
|
5728
|
+
self.writeStatusFile(section_name, self._renamings)
|
|
5729
|
+
|
|
5730
|
+
return success
|
|
4634
5731
|
|
|
4635
5732
|
# end method definition
|
|
4636
5733
|
|
|
4637
|
-
def processItems(self, items: list):
|
|
5734
|
+
def processItems(self, items: list, section_name: str = "items") -> bool:
|
|
4638
5735
|
"""Process items specified in payload and create them in Extended ECM.
|
|
4639
5736
|
|
|
4640
5737
|
Args:
|
|
4641
|
-
otcs: OTCS object
|
|
4642
5738
|
items: list of items to create (need this as parameter as we
|
|
4643
5739
|
have multiple lists)
|
|
4644
|
-
Return:
|
|
5740
|
+
Return:
|
|
5741
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
4645
5742
|
"""
|
|
4646
5743
|
|
|
5744
|
+
if not items:
|
|
5745
|
+
logger.info(
|
|
5746
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
5747
|
+
)
|
|
5748
|
+
return True
|
|
5749
|
+
|
|
5750
|
+
# if this payload section has been processed successfully before we can return True
|
|
5751
|
+
# and skip processing once more
|
|
5752
|
+
if self.checkStatusFile(section_name):
|
|
5753
|
+
return True
|
|
5754
|
+
|
|
5755
|
+
success: bool = True
|
|
5756
|
+
|
|
4647
5757
|
for item in items:
|
|
4648
5758
|
if not "name" in item:
|
|
4649
5759
|
logger.error("Item needs a name. Skipping...")
|
|
@@ -4676,6 +5786,7 @@ class Payload(object):
|
|
|
4676
5786
|
item_name, parent_nickname
|
|
4677
5787
|
)
|
|
4678
5788
|
)
|
|
5789
|
+
success = False
|
|
4679
5790
|
continue
|
|
4680
5791
|
else: # use parent_path and Enterprise Volume
|
|
4681
5792
|
parent_node = self._otcs.getNodeByVolumeAndPath(141, parent_path)
|
|
@@ -4687,6 +5798,7 @@ class Payload(object):
|
|
|
4687
5798
|
item_name
|
|
4688
5799
|
)
|
|
4689
5800
|
)
|
|
5801
|
+
success = False
|
|
4690
5802
|
continue
|
|
4691
5803
|
|
|
4692
5804
|
original_nickname = item.get("original_nickname")
|
|
@@ -4702,6 +5814,7 @@ class Payload(object):
|
|
|
4702
5814
|
item_name, original_nickname
|
|
4703
5815
|
)
|
|
4704
5816
|
)
|
|
5817
|
+
success = False
|
|
4705
5818
|
continue
|
|
4706
5819
|
elif original_path:
|
|
4707
5820
|
original_node = self._otcs.getNodeByVolumeAndPath(141, original_path)
|
|
@@ -4713,12 +5826,14 @@ class Payload(object):
|
|
|
4713
5826
|
item_name
|
|
4714
5827
|
)
|
|
4715
5828
|
)
|
|
5829
|
+
success = False
|
|
4716
5830
|
continue
|
|
4717
5831
|
else:
|
|
4718
5832
|
original_id = 0
|
|
4719
5833
|
|
|
4720
5834
|
if not "type" in item:
|
|
4721
5835
|
logger.error("Item -> {} needs a type. Skipping...".format(item_name))
|
|
5836
|
+
success = False
|
|
4722
5837
|
continue
|
|
4723
5838
|
|
|
4724
5839
|
item_type = item.get("type")
|
|
@@ -4734,6 +5849,7 @@ class Payload(object):
|
|
|
4734
5849
|
item_name
|
|
4735
5850
|
)
|
|
4736
5851
|
)
|
|
5852
|
+
success = False
|
|
4737
5853
|
continue
|
|
4738
5854
|
case 1: # Shortcut
|
|
4739
5855
|
if original_id == 0:
|
|
@@ -4742,12 +5858,15 @@ class Payload(object):
|
|
|
4742
5858
|
item_name
|
|
4743
5859
|
)
|
|
4744
5860
|
)
|
|
5861
|
+
success = False
|
|
4745
5862
|
continue
|
|
4746
5863
|
|
|
4747
5864
|
# Check if an item with the same name does already exist.
|
|
4748
5865
|
# This can also be the case if the python container runs a 2nd time.
|
|
4749
5866
|
# For this reason we are also not issuing an error but just an info (False):
|
|
4750
|
-
response = self._otcs.getNodeByParentAndName(
|
|
5867
|
+
response = self._otcs.getNodeByParentAndName(
|
|
5868
|
+
parent_id, item_name, show_error=False
|
|
5869
|
+
)
|
|
4751
5870
|
if self._otcs.getResultValue(response, "name") == item_name:
|
|
4752
5871
|
logger.info(
|
|
4753
5872
|
"Item with name -> {} does already exist in parent folder with ID -> {}".format(
|
|
@@ -4760,10 +5879,18 @@ class Payload(object):
|
|
|
4760
5879
|
)
|
|
4761
5880
|
if not response:
|
|
4762
5881
|
logger.error("Failed to create item -> {}.".format(item_name))
|
|
5882
|
+
success = False
|
|
5883
|
+
|
|
5884
|
+
if success:
|
|
5885
|
+
self.writeStatusFile(section_name, items)
|
|
5886
|
+
|
|
5887
|
+
return success
|
|
4763
5888
|
|
|
4764
5889
|
# end method definition
|
|
4765
5890
|
|
|
4766
|
-
def processPermissions(
|
|
5891
|
+
def processPermissions(
|
|
5892
|
+
self, permissions: list, section_name: str = "permissions"
|
|
5893
|
+
) -> bool:
|
|
4767
5894
|
"""Process items specified in payload and upadate permissions.
|
|
4768
5895
|
|
|
4769
5896
|
Args:
|
|
@@ -4791,9 +5918,23 @@ class Payload(object):
|
|
|
4791
5918
|
apply_to = 2
|
|
4792
5919
|
}
|
|
4793
5920
|
|
|
4794
|
-
Return:
|
|
5921
|
+
Return:
|
|
5922
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
4795
5923
|
"""
|
|
4796
5924
|
|
|
5925
|
+
if not permissions:
|
|
5926
|
+
logger.info(
|
|
5927
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
5928
|
+
)
|
|
5929
|
+
return True
|
|
5930
|
+
|
|
5931
|
+
# if this payload section has been processed successfully before we can return True
|
|
5932
|
+
# and skip processing once more
|
|
5933
|
+
if self.checkStatusFile(section_name):
|
|
5934
|
+
return True
|
|
5935
|
+
|
|
5936
|
+
success: bool = True
|
|
5937
|
+
|
|
4797
5938
|
for permission in permissions:
|
|
4798
5939
|
if (
|
|
4799
5940
|
not "path" in permission
|
|
@@ -4801,6 +5942,7 @@ class Payload(object):
|
|
|
4801
5942
|
and not "nickname" in permission
|
|
4802
5943
|
):
|
|
4803
5944
|
logger.error("Item to change permission is not specified. Skipping...")
|
|
5945
|
+
success = False
|
|
4804
5946
|
continue
|
|
4805
5947
|
|
|
4806
5948
|
# Check if element has been disabled in payload (enabled = false).
|
|
@@ -4827,6 +5969,7 @@ class Payload(object):
|
|
|
4827
5969
|
volume_type
|
|
4828
5970
|
)
|
|
4829
5971
|
)
|
|
5972
|
+
success = False
|
|
4830
5973
|
continue
|
|
4831
5974
|
|
|
4832
5975
|
# Check if "path" is in payload and not empty list
|
|
@@ -4842,6 +5985,7 @@ class Payload(object):
|
|
|
4842
5985
|
node_id = self._otcs.getResultValue(node, "id")
|
|
4843
5986
|
if not node_id:
|
|
4844
5987
|
logger.error("Path -> {} does not exist. Skipping...".format(path))
|
|
5988
|
+
success = False
|
|
4845
5989
|
continue
|
|
4846
5990
|
|
|
4847
5991
|
# Check if "nickname" is in payload and not empty string:
|
|
@@ -4856,11 +6000,13 @@ class Payload(object):
|
|
|
4856
6000
|
node_id = self._otcs.getResultValue(node, "id")
|
|
4857
6001
|
if not node_id:
|
|
4858
6002
|
logger.error("Nickname -> {} does not exist. Skipping...")
|
|
6003
|
+
success = False
|
|
4859
6004
|
continue
|
|
4860
6005
|
|
|
4861
6006
|
# Now we should have a value for node_id:
|
|
4862
6007
|
if not node_id:
|
|
4863
6008
|
logger.error("No node ID found! Skipping permission...")
|
|
6009
|
+
success = False
|
|
4864
6010
|
continue
|
|
4865
6011
|
else:
|
|
4866
6012
|
node_name = self._otcs.getResultValue(node, "name")
|
|
@@ -4892,6 +6038,7 @@ class Payload(object):
|
|
|
4892
6038
|
node_id
|
|
4893
6039
|
)
|
|
4894
6040
|
)
|
|
6041
|
+
success = False
|
|
4895
6042
|
|
|
4896
6043
|
# 2. Process Owner Group Permissions
|
|
4897
6044
|
if "owner_group_permissions" in permission:
|
|
@@ -4910,6 +6057,7 @@ class Payload(object):
|
|
|
4910
6057
|
node_id
|
|
4911
6058
|
)
|
|
4912
6059
|
)
|
|
6060
|
+
success = False
|
|
4913
6061
|
|
|
4914
6062
|
# 3. Process Public Permissions
|
|
4915
6063
|
if "public_permissions" in permission:
|
|
@@ -4928,6 +6076,7 @@ class Payload(object):
|
|
|
4928
6076
|
node_id
|
|
4929
6077
|
)
|
|
4930
6078
|
)
|
|
6079
|
+
success = False
|
|
4931
6080
|
continue
|
|
4932
6081
|
|
|
4933
6082
|
# 3. Process Assigned User Permissions (if specified and not empty)
|
|
@@ -4938,6 +6087,7 @@ class Payload(object):
|
|
|
4938
6087
|
logger.error(
|
|
4939
6088
|
"Missing user name or permissions in user permission specificiation. Cannot set user permissions. Skipping..."
|
|
4940
6089
|
)
|
|
6090
|
+
success = False
|
|
4941
6091
|
continue
|
|
4942
6092
|
user_name = user["name"]
|
|
4943
6093
|
permissions = user["permissions"]
|
|
@@ -4948,6 +6098,7 @@ class Payload(object):
|
|
|
4948
6098
|
user_name
|
|
4949
6099
|
)
|
|
4950
6100
|
)
|
|
6101
|
+
success = False
|
|
4951
6102
|
continue
|
|
4952
6103
|
user_id = otcs_user["data"][0]["id"]
|
|
4953
6104
|
logger.info(
|
|
@@ -4964,6 +6115,7 @@ class Payload(object):
|
|
|
4964
6115
|
node_id
|
|
4965
6116
|
)
|
|
4966
6117
|
)
|
|
6118
|
+
success = False
|
|
4967
6119
|
|
|
4968
6120
|
# 4. Process Assigned Group Permissions (if specified and not empty)
|
|
4969
6121
|
if "groups" in permission and permission["groups"]:
|
|
@@ -4988,6 +6140,7 @@ class Payload(object):
|
|
|
4988
6140
|
group_name
|
|
4989
6141
|
)
|
|
4990
6142
|
)
|
|
6143
|
+
success = False
|
|
4991
6144
|
continue
|
|
4992
6145
|
group_id = otcs_group["data"][0]["id"]
|
|
4993
6146
|
response = self._otcs.assignPermission(
|
|
@@ -4999,21 +6152,43 @@ class Payload(object):
|
|
|
4999
6152
|
node_id
|
|
5000
6153
|
)
|
|
5001
6154
|
)
|
|
6155
|
+
success = False
|
|
6156
|
+
|
|
6157
|
+
if success:
|
|
6158
|
+
self.writeStatusFile(section_name, permissions)
|
|
6159
|
+
|
|
6160
|
+
return success
|
|
5002
6161
|
|
|
5003
6162
|
# end method definition
|
|
5004
6163
|
|
|
5005
|
-
def processAssignments(self):
|
|
6164
|
+
def processAssignments(self, section_name: str = "assignments") -> bool:
|
|
5006
6165
|
"""Process assignments specified in payload and assign items (such as workspaces and
|
|
5007
6166
|
items withnicknames) to users or groups.
|
|
5008
6167
|
|
|
5009
|
-
Args:
|
|
5010
|
-
|
|
6168
|
+
Args:
|
|
6169
|
+
None
|
|
6170
|
+
Return:
|
|
6171
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
5011
6172
|
"""
|
|
5012
6173
|
|
|
6174
|
+
if not self._assignments:
|
|
6175
|
+
logger.info(
|
|
6176
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
6177
|
+
)
|
|
6178
|
+
return True
|
|
6179
|
+
|
|
6180
|
+
# if this payload section has been processed successfully before we can return True
|
|
6181
|
+
# and skip processing once more
|
|
6182
|
+
if self.checkStatusFile(section_name):
|
|
6183
|
+
return True
|
|
6184
|
+
|
|
6185
|
+
success: bool = True
|
|
6186
|
+
|
|
5013
6187
|
for assignment in self._assignments:
|
|
5014
6188
|
# Sanity check: we need a subject - it's mandatory:
|
|
5015
6189
|
if not "subject" in assignment:
|
|
5016
6190
|
logger.error("Assignment needs a subject! Skipping assignment...")
|
|
6191
|
+
success = False
|
|
5017
6192
|
continue
|
|
5018
6193
|
subject = assignment["subject"]
|
|
5019
6194
|
|
|
@@ -5042,6 +6217,7 @@ class Payload(object):
|
|
|
5042
6217
|
subject
|
|
5043
6218
|
)
|
|
5044
6219
|
)
|
|
6220
|
+
success = False
|
|
5045
6221
|
continue
|
|
5046
6222
|
# Check if a workspace is specified for the assignment and check it does exist:
|
|
5047
6223
|
if "workspace" in assignment and assignment["workspace"]:
|
|
@@ -5053,14 +6229,23 @@ class Payload(object):
|
|
|
5053
6229
|
),
|
|
5054
6230
|
None,
|
|
5055
6231
|
)
|
|
5056
|
-
if not workspace
|
|
6232
|
+
if not workspace:
|
|
6233
|
+
logger.error(
|
|
6234
|
+
"Assignment -> {} has specified a not existing workspace -> {}! Skipping assignment...".format(
|
|
6235
|
+
subject, assignment["workspace"]
|
|
6236
|
+
)
|
|
6237
|
+
)
|
|
6238
|
+
success = False
|
|
6239
|
+
continue
|
|
6240
|
+
node_id = self.determineWorkspaceID(workspace)
|
|
6241
|
+
if not node_id:
|
|
5057
6242
|
logger.error(
|
|
5058
6243
|
"Assignment -> {} has specified a not existing workspace -> {}! Skipping assignment...".format(
|
|
5059
6244
|
subject, assignment["workspace"]
|
|
5060
6245
|
)
|
|
5061
6246
|
)
|
|
6247
|
+
success = False
|
|
5062
6248
|
continue
|
|
5063
|
-
node_id = workspace["nodeId"]
|
|
5064
6249
|
# If we don't have a workspace then check if a nickname is specified for the assignment:
|
|
5065
6250
|
elif "nickname" in assignment:
|
|
5066
6251
|
response = self._otcs.getNodeFromNickname(assignment["nickname"])
|
|
@@ -5072,6 +6257,7 @@ class Payload(object):
|
|
|
5072
6257
|
assignment["nickname"]
|
|
5073
6258
|
)
|
|
5074
6259
|
)
|
|
6260
|
+
success = False
|
|
5075
6261
|
continue
|
|
5076
6262
|
else:
|
|
5077
6263
|
logger.error(
|
|
@@ -5079,6 +6265,7 @@ class Payload(object):
|
|
|
5079
6265
|
subject
|
|
5080
6266
|
)
|
|
5081
6267
|
)
|
|
6268
|
+
success = False
|
|
5082
6269
|
continue
|
|
5083
6270
|
|
|
5084
6271
|
assignees = []
|
|
@@ -5101,6 +6288,7 @@ class Payload(object):
|
|
|
5101
6288
|
group_assignee
|
|
5102
6289
|
)
|
|
5103
6290
|
)
|
|
6291
|
+
success = False
|
|
5104
6292
|
continue
|
|
5105
6293
|
if not "id" in group:
|
|
5106
6294
|
logger.error(
|
|
@@ -5108,6 +6296,7 @@ class Payload(object):
|
|
|
5108
6296
|
group_assignee
|
|
5109
6297
|
)
|
|
5110
6298
|
)
|
|
6299
|
+
success = False
|
|
5111
6300
|
continue
|
|
5112
6301
|
group_id = group["id"]
|
|
5113
6302
|
# add the group ID to the assignee list:
|
|
@@ -5127,6 +6316,7 @@ class Payload(object):
|
|
|
5127
6316
|
user_assignee
|
|
5128
6317
|
)
|
|
5129
6318
|
)
|
|
6319
|
+
success = False
|
|
5130
6320
|
continue
|
|
5131
6321
|
if not "id" in user:
|
|
5132
6322
|
logger.error(
|
|
@@ -5134,6 +6324,7 @@ class Payload(object):
|
|
|
5134
6324
|
user_assignee
|
|
5135
6325
|
)
|
|
5136
6326
|
)
|
|
6327
|
+
success = False
|
|
5137
6328
|
continue
|
|
5138
6329
|
user_id = user["id"]
|
|
5139
6330
|
# add the group ID to the assignee list:
|
|
@@ -5145,7 +6336,8 @@ class Payload(object):
|
|
|
5145
6336
|
subject, node_id
|
|
5146
6337
|
)
|
|
5147
6338
|
)
|
|
5148
|
-
|
|
6339
|
+
success = False
|
|
6340
|
+
continue
|
|
5149
6341
|
|
|
5150
6342
|
response = self._otcs.assignItemToUserGroup(
|
|
5151
6343
|
node_id, subject, instruction, assignees
|
|
@@ -5156,6 +6348,12 @@ class Payload(object):
|
|
|
5156
6348
|
subject, node_id, assignees
|
|
5157
6349
|
)
|
|
5158
6350
|
)
|
|
6351
|
+
success = False
|
|
6352
|
+
|
|
6353
|
+
if success:
|
|
6354
|
+
self.writeStatusFile(section_name, self._assignments)
|
|
6355
|
+
|
|
6356
|
+
return success
|
|
5159
6357
|
|
|
5160
6358
|
# end method definition
|
|
5161
6359
|
|
|
@@ -5165,7 +6363,8 @@ class Payload(object):
|
|
|
5165
6363
|
license_feature: str,
|
|
5166
6364
|
license_name: str,
|
|
5167
6365
|
user_specific_payload_field: str = "licenses",
|
|
5168
|
-
|
|
6366
|
+
section_name: str = "userLicenses",
|
|
6367
|
+
) -> bool:
|
|
5169
6368
|
"""Assign a specific OTDS license feature to all Extended ECM users.
|
|
5170
6369
|
This method is used for OTIV and Extended ECM licenses.
|
|
5171
6370
|
|
|
@@ -5175,20 +6374,34 @@ class Payload(object):
|
|
|
5175
6374
|
license_name: Name of the license Key (e.g. "EXTENDED_ECM" or "INTELLIGENT_VIEWING")
|
|
5176
6375
|
user_specific_payload_field: name of the user specific field in payload
|
|
5177
6376
|
(if empty it will be ignored)
|
|
5178
|
-
Return:
|
|
6377
|
+
Return:
|
|
6378
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
5179
6379
|
"""
|
|
5180
6380
|
|
|
6381
|
+
if not self._users:
|
|
6382
|
+
logger.info(
|
|
6383
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
6384
|
+
)
|
|
6385
|
+
return True
|
|
6386
|
+
|
|
6387
|
+
# if this payload section has been processed successfully before we can return True
|
|
6388
|
+
# and skip processing once more
|
|
6389
|
+
if self.checkStatusFile(section_name):
|
|
6390
|
+
return True
|
|
6391
|
+
|
|
6392
|
+
success: bool = True
|
|
6393
|
+
|
|
5181
6394
|
otds_resource = self._otds.getResource(resource_name)
|
|
5182
6395
|
if not otds_resource:
|
|
5183
6396
|
logger.error(
|
|
5184
6397
|
"OTDS Resource -> {} not found. Cannot assign licenses to users."
|
|
5185
6398
|
)
|
|
5186
|
-
return
|
|
6399
|
+
return False
|
|
5187
6400
|
|
|
5188
6401
|
user_partition = self._otcs.config()["partition"]
|
|
5189
6402
|
if not user_partition:
|
|
5190
6403
|
logger.error("OTCS user partition not found in OTDS!")
|
|
5191
|
-
return
|
|
6404
|
+
return False
|
|
5192
6405
|
|
|
5193
6406
|
for user in self._users:
|
|
5194
6407
|
user_name = user["name"]
|
|
@@ -5211,36 +6424,70 @@ class Payload(object):
|
|
|
5211
6424
|
else: # use the default feature from the actual parameter
|
|
5212
6425
|
user_license_feature = [license_feature]
|
|
5213
6426
|
|
|
5214
|
-
for
|
|
6427
|
+
for license_feature in user_license_feature:
|
|
6428
|
+
if self._otds.isUserLicensed(
|
|
6429
|
+
user_name=user_name,
|
|
6430
|
+
resource_id=otds_resource["resourceID"],
|
|
6431
|
+
license_feature=license_feature,
|
|
6432
|
+
license_name=license_name,
|
|
6433
|
+
):
|
|
6434
|
+
logger.info(
|
|
6435
|
+
"User -> {} is already licensed for -> {} ({})".format(
|
|
6436
|
+
user_name, license_name, license_feature
|
|
6437
|
+
)
|
|
6438
|
+
)
|
|
6439
|
+
continue
|
|
5215
6440
|
assigned_license = self._otds.assignUserToLicense(
|
|
5216
6441
|
user_partition,
|
|
5217
6442
|
user_name, # we want the plain login name here
|
|
5218
6443
|
otds_resource["resourceID"],
|
|
5219
|
-
|
|
6444
|
+
license_feature,
|
|
5220
6445
|
license_name,
|
|
5221
6446
|
)
|
|
5222
6447
|
|
|
5223
6448
|
if not assigned_license:
|
|
5224
6449
|
logger.error(
|
|
5225
6450
|
"Failed to assign license feature -> {} to user -> {}!".format(
|
|
5226
|
-
|
|
6451
|
+
license_feature, user_name
|
|
5227
6452
|
)
|
|
5228
6453
|
)
|
|
6454
|
+
success = False
|
|
6455
|
+
|
|
6456
|
+
if success:
|
|
6457
|
+
self.writeStatusFile(section_name, self._users)
|
|
6458
|
+
|
|
6459
|
+
return success
|
|
5229
6460
|
|
|
5230
6461
|
# end method definition
|
|
5231
6462
|
|
|
5232
|
-
def processExecPodCommands(self):
|
|
6463
|
+
def processExecPodCommands(self, section_name: str = "execPodCommands") -> bool:
|
|
5233
6464
|
"""Process commands that should be executed in the Kubernetes pods.
|
|
5234
6465
|
|
|
5235
|
-
Args:
|
|
5236
|
-
|
|
6466
|
+
Args:
|
|
6467
|
+
None
|
|
6468
|
+
Return:
|
|
6469
|
+
None
|
|
5237
6470
|
"""
|
|
5238
6471
|
|
|
6472
|
+
if not self._exec_pod_commands:
|
|
6473
|
+
logger.info(
|
|
6474
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
6475
|
+
)
|
|
6476
|
+
return True
|
|
6477
|
+
|
|
6478
|
+
# if this payload section has been processed successfully before we can return True
|
|
6479
|
+
# and skip processing once more
|
|
6480
|
+
if self.checkStatusFile(section_name):
|
|
6481
|
+
return True
|
|
6482
|
+
|
|
6483
|
+
success: bool = True
|
|
6484
|
+
|
|
5239
6485
|
for exec_pod_command in self._exec_pod_commands:
|
|
5240
6486
|
if not "pod_name" in exec_pod_command:
|
|
5241
6487
|
logger.error(
|
|
5242
6488
|
"To execute a command in a pod the pod name needs to be specified in the payload! Skipping to next pod command..."
|
|
5243
6489
|
)
|
|
6490
|
+
success = False
|
|
5244
6491
|
continue
|
|
5245
6492
|
pod_name = exec_pod_command["pod_name"]
|
|
5246
6493
|
|
|
@@ -5250,6 +6497,7 @@ class Payload(object):
|
|
|
5250
6497
|
pod_name
|
|
5251
6498
|
)
|
|
5252
6499
|
)
|
|
6500
|
+
success = False
|
|
5253
6501
|
continue
|
|
5254
6502
|
command = exec_pod_command["command"]
|
|
5255
6503
|
|
|
@@ -5304,17 +6552,37 @@ class Payload(object):
|
|
|
5304
6552
|
)
|
|
5305
6553
|
)
|
|
5306
6554
|
|
|
6555
|
+
if success:
|
|
6556
|
+
self.writeStatusFile(section_name, self._exec_pod_commands)
|
|
6557
|
+
|
|
6558
|
+
return success
|
|
6559
|
+
|
|
5307
6560
|
# end method definition
|
|
5308
6561
|
|
|
5309
|
-
def processDocumentGenerators(
|
|
6562
|
+
def processDocumentGenerators(
|
|
6563
|
+
self, section_name: str = "documentGenerators"
|
|
6564
|
+
) -> bool:
|
|
5310
6565
|
"""Generate documents for a defined workspace type based on template
|
|
5311
6566
|
|
|
5312
6567
|
Args:
|
|
5313
|
-
|
|
6568
|
+
None
|
|
5314
6569
|
Returns:
|
|
5315
|
-
|
|
6570
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
5316
6571
|
"""
|
|
5317
6572
|
|
|
6573
|
+
if not self._doc_generators:
|
|
6574
|
+
logger.info(
|
|
6575
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
6576
|
+
)
|
|
6577
|
+
return True
|
|
6578
|
+
|
|
6579
|
+
# if this payload section has been processed successfully before we can return True
|
|
6580
|
+
# and skip processing once more
|
|
6581
|
+
if self.checkStatusFile(section_name):
|
|
6582
|
+
return True
|
|
6583
|
+
|
|
6584
|
+
success: bool = True
|
|
6585
|
+
|
|
5318
6586
|
# save admin credentials for later switch back to admin user:
|
|
5319
6587
|
admin_credentials = self._otcs.credentials()
|
|
5320
6588
|
authenticated_user = "admin"
|
|
@@ -5324,6 +6592,7 @@ class Payload(object):
|
|
|
5324
6592
|
logger.error(
|
|
5325
6593
|
"To generate documents for workspaces the workspace type needs to be specified in the payload! Skipping to next document generator..."
|
|
5326
6594
|
)
|
|
6595
|
+
success = False
|
|
5327
6596
|
continue
|
|
5328
6597
|
workspace_type = doc_generator["workspace_type"]
|
|
5329
6598
|
|
|
@@ -5333,6 +6602,7 @@ class Payload(object):
|
|
|
5333
6602
|
workspace_type
|
|
5334
6603
|
)
|
|
5335
6604
|
)
|
|
6605
|
+
success = False
|
|
5336
6606
|
continue
|
|
5337
6607
|
template_path = doc_generator["template_path"]
|
|
5338
6608
|
|
|
@@ -5352,6 +6622,7 @@ class Payload(object):
|
|
|
5352
6622
|
workspace_type
|
|
5353
6623
|
)
|
|
5354
6624
|
)
|
|
6625
|
+
success = False
|
|
5355
6626
|
continue
|
|
5356
6627
|
classification_path = doc_generator["classification_path"]
|
|
5357
6628
|
|
|
@@ -5361,6 +6632,7 @@ class Payload(object):
|
|
|
5361
6632
|
workspace_type
|
|
5362
6633
|
)
|
|
5363
6634
|
)
|
|
6635
|
+
success = False
|
|
5364
6636
|
continue
|
|
5365
6637
|
category_name = doc_generator["category_name"]
|
|
5366
6638
|
|
|
@@ -5370,6 +6642,7 @@ class Payload(object):
|
|
|
5370
6642
|
workspace_type
|
|
5371
6643
|
)
|
|
5372
6644
|
)
|
|
6645
|
+
success = False
|
|
5373
6646
|
continue
|
|
5374
6647
|
attributes = doc_generator["attributes"]
|
|
5375
6648
|
|
|
@@ -5419,6 +6692,7 @@ class Payload(object):
|
|
|
5419
6692
|
)
|
|
5420
6693
|
)
|
|
5421
6694
|
admin_context = True
|
|
6695
|
+
success = False
|
|
5422
6696
|
else:
|
|
5423
6697
|
admin_context = True
|
|
5424
6698
|
|
|
@@ -5472,6 +6746,7 @@ class Payload(object):
|
|
|
5472
6746
|
attribute_value
|
|
5473
6747
|
)
|
|
5474
6748
|
)
|
|
6749
|
+
success = False
|
|
5475
6750
|
continue
|
|
5476
6751
|
attribute_value = user["data"][0]["id"]
|
|
5477
6752
|
|
|
@@ -5532,6 +6807,7 @@ class Payload(object):
|
|
|
5532
6807
|
document_name, workspace_name
|
|
5533
6808
|
)
|
|
5534
6809
|
)
|
|
6810
|
+
success = False
|
|
5535
6811
|
else:
|
|
5536
6812
|
logger.info(
|
|
5537
6813
|
"Successfully generated document -> {} in workspace -> {}".format(
|
|
@@ -5554,6 +6830,11 @@ class Payload(object):
|
|
|
5554
6830
|
# True = force new login with new user
|
|
5555
6831
|
cookie = self._otcs.authenticate(True)
|
|
5556
6832
|
|
|
6833
|
+
if success:
|
|
6834
|
+
self.writeStatusFile(section_name, self._doc_generators)
|
|
6835
|
+
|
|
6836
|
+
return success
|
|
6837
|
+
|
|
5557
6838
|
# end method definition
|
|
5558
6839
|
|
|
5559
6840
|
def initSAP(
|
|
@@ -5620,14 +6901,28 @@ class Payload(object):
|
|
|
5620
6901
|
|
|
5621
6902
|
# end method definition
|
|
5622
6903
|
|
|
5623
|
-
def processSAPRFCs(self, sap_object: object):
|
|
6904
|
+
def processSAPRFCs(self, sap_object: object, section_name: str = "sapRFCs") -> bool:
|
|
5624
6905
|
"""Process SAP RFCs in payload and run them in SAP S/4HANA.
|
|
5625
6906
|
|
|
5626
6907
|
Args:
|
|
5627
6908
|
sap_object: SAP object
|
|
5628
|
-
Return:
|
|
6909
|
+
Return:
|
|
6910
|
+
bool: True if payload has been processed without errors, False otherwise
|
|
5629
6911
|
"""
|
|
5630
6912
|
|
|
6913
|
+
if not self._doc_generators:
|
|
6914
|
+
logger.info(
|
|
6915
|
+
"Payload section -> {} is empty. Skipping...".format(section_name)
|
|
6916
|
+
)
|
|
6917
|
+
return True
|
|
6918
|
+
|
|
6919
|
+
# if this payload section has been processed successfully before we can return True
|
|
6920
|
+
# and skip processing once more
|
|
6921
|
+
if self.checkStatusFile(section_name):
|
|
6922
|
+
return True
|
|
6923
|
+
|
|
6924
|
+
success: bool = True
|
|
6925
|
+
|
|
5631
6926
|
for sap_rfc in self._sap_rfcs:
|
|
5632
6927
|
rfc_name = sap_rfc["name"]
|
|
5633
6928
|
|
|
@@ -5670,6 +6965,7 @@ class Payload(object):
|
|
|
5670
6965
|
result = sap_object.call(rfc_name, rfc_call_options, rfc_params)
|
|
5671
6966
|
if result == None:
|
|
5672
6967
|
logger.error("Failed to call SAP RFC -> {}".format(rfc_name))
|
|
6968
|
+
success = False
|
|
5673
6969
|
else:
|
|
5674
6970
|
logger.info(
|
|
5675
6971
|
"Successfully called RFC -> {}. Result -> {}".format(
|
|
@@ -5677,6 +6973,11 @@ class Payload(object):
|
|
|
5677
6973
|
)
|
|
5678
6974
|
)
|
|
5679
6975
|
|
|
6976
|
+
if success:
|
|
6977
|
+
self.writeStatusFile(section_name, self._sap_rfcs)
|
|
6978
|
+
|
|
6979
|
+
return success
|
|
6980
|
+
|
|
5680
6981
|
# end method definition
|
|
5681
6982
|
|
|
5682
6983
|
def getPayload(self) -> dict:
|