pyxecm 1.3.0__py3-none-any.whl → 1.5__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 +3 -0
- pyxecm/coreshare.py +2636 -0
- pyxecm/customizer/__init__.py +6 -0
- pyxecm/customizer/browser_automation.py +231 -56
- pyxecm/customizer/customizer.py +466 -235
- pyxecm/customizer/k8s.py +49 -27
- pyxecm/customizer/m365.py +1183 -263
- pyxecm/customizer/payload.py +13854 -5368
- pyxecm/customizer/pht.py +503 -0
- pyxecm/customizer/salesforce.py +1782 -0
- pyxecm/customizer/sap.py +5 -5
- pyxecm/customizer/servicenow.py +1221 -0
- pyxecm/customizer/successfactors.py +1056 -0
- pyxecm/customizer/translate.py +2 -2
- pyxecm/helper/__init__.py +2 -0
- pyxecm/helper/assoc.py +27 -7
- pyxecm/helper/data.py +1527 -0
- pyxecm/helper/web.py +189 -25
- pyxecm/helper/xml.py +244 -40
- pyxecm/otac.py +311 -25
- pyxecm/otcs.py +3866 -1103
- pyxecm/otds.py +397 -150
- pyxecm/otiv.py +1 -1
- pyxecm/otmm.py +808 -0
- pyxecm/otpd.py +17 -12
- {pyxecm-1.3.0.dist-info → pyxecm-1.5.dist-info}/METADATA +4 -1
- pyxecm-1.5.dist-info/RECORD +30 -0
- {pyxecm-1.3.0.dist-info → pyxecm-1.5.dist-info}/WHEEL +1 -1
- pyxecm-1.3.0.dist-info/RECORD +0 -23
- {pyxecm-1.3.0.dist-info → pyxecm-1.5.dist-info}/LICENSE +0 -0
- {pyxecm-1.3.0.dist-info → pyxecm-1.5.dist-info}/top_level.txt +0 -0
pyxecm/customizer/customizer.py
CHANGED
|
@@ -41,7 +41,7 @@ customization_run: Central function to initiate the customization
|
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
43
|
__author__ = "Dr. Marc Diefenbruch"
|
|
44
|
-
__copyright__ = "Copyright
|
|
44
|
+
__copyright__ = "Copyright 2024, OpenText"
|
|
45
45
|
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
46
46
|
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
47
47
|
__email__ = "mdiefenb@opentext.com"
|
|
@@ -59,7 +59,7 @@ import requests
|
|
|
59
59
|
|
|
60
60
|
# OpenText specific modules:
|
|
61
61
|
import yaml
|
|
62
|
-
from pyxecm import OTAC, OTCS, OTDS, OTIV, OTPD
|
|
62
|
+
from pyxecm import OTAC, OTCS, OTDS, OTIV, OTPD, OTMM, CoreShare
|
|
63
63
|
from pyxecm.customizer.k8s import K8s
|
|
64
64
|
from pyxecm.customizer.m365 import M365
|
|
65
65
|
from pyxecm.customizer.payload import Payload
|
|
@@ -74,7 +74,7 @@ class CustomizerSettings:
|
|
|
74
74
|
"""Class to manage settings"""
|
|
75
75
|
|
|
76
76
|
placeholder_values: dict = field(default_factory=dict)
|
|
77
|
-
stop_on_error: bool = os.environ.get("
|
|
77
|
+
stop_on_error: bool = os.environ.get("STOP_ON_ERROR", "false").lower() == "true"
|
|
78
78
|
cust_log_file: str = "/tmp/customizing.log"
|
|
79
79
|
customizer_start_time = customizer_end_time = datetime.now()
|
|
80
80
|
|
|
@@ -123,6 +123,7 @@ class CustomizerSettingsOTCS:
|
|
|
123
123
|
port: int = os.environ.get("OTCS_SERVICE_PORT_OTCS", 8080)
|
|
124
124
|
port_backend: int = os.environ.get("OTCS_SERVICE_PORT_OTCS", 8080)
|
|
125
125
|
port_frontend: int = 80
|
|
126
|
+
base_path: str = "/cs/cs"
|
|
126
127
|
admin: str = os.environ.get("OTCS_ADMIN", "admin")
|
|
127
128
|
password: str = os.environ.get("OTCS_PASSWORD")
|
|
128
129
|
partition: str = os.environ.get("OTCS_PARTITION", "Content Server Members")
|
|
@@ -142,7 +143,11 @@ class CustomizerSettingsOTCS:
|
|
|
142
143
|
replicas_frontend = 0
|
|
143
144
|
replicas_backend = 0
|
|
144
145
|
|
|
146
|
+
# Add configuration options for Customizer behaviour
|
|
145
147
|
update_admin_user: bool = True
|
|
148
|
+
upload_config_files: bool = True
|
|
149
|
+
upload_status_files: bool = True
|
|
150
|
+
upload_log_file: bool = True
|
|
146
151
|
|
|
147
152
|
|
|
148
153
|
@dataclass
|
|
@@ -231,7 +236,23 @@ class CustomizerSettingsM365:
|
|
|
231
236
|
password: str = os.environ.get("O365_PASSWORD", "")
|
|
232
237
|
domain: str = os.environ.get("O365_DOMAIN", "")
|
|
233
238
|
sku_id: str = os.environ.get("O365_SKU_ID", "c7df2760-2c81-4ef7-b578-5b5392b571df")
|
|
234
|
-
teams_app_name: str = "OpenText Extended ECM"
|
|
239
|
+
teams_app_name: str = os.environ.get("O365_TEAMS_APP_NAME", "OpenText Extended ECM")
|
|
240
|
+
teams_app_external_id: str = os.environ.get(
|
|
241
|
+
"O365_TEAMS_APP_ID", "dd4af790-d8ff-47a0-87ad-486318272c7a"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@dataclass
|
|
246
|
+
class CustomizerSettingsCoreShare:
|
|
247
|
+
"""Class for Core Share related settings"""
|
|
248
|
+
|
|
249
|
+
enabled: bool = os.environ.get("CORE_SHARE_ENABLED", "false").lower() == "true"
|
|
250
|
+
base_url: str = os.environ.get("CORE_SHARE_BASE_URL", "https://core.opentext.com")
|
|
251
|
+
sso_url: str = os.environ.get("CORE_SHARE_SSO_URL", "https://sso.core.opentext.com")
|
|
252
|
+
client_id: str = os.environ.get("CORE_SHARE_CLIENT_ID", "")
|
|
253
|
+
client_secret = os.environ.get("CORE_SHARE_CLIENT_SECRET", "")
|
|
254
|
+
username: str = os.environ.get("CORE_SHARE_USERNAME", "")
|
|
255
|
+
password: str = os.environ.get("CORE_SHARE_PASSWORD", "")
|
|
235
256
|
|
|
236
257
|
|
|
237
258
|
@dataclass
|
|
@@ -258,6 +279,7 @@ class Customizer:
|
|
|
258
279
|
k8s: CustomizerSettingsK8S = CustomizerSettingsK8S(),
|
|
259
280
|
otawp: CustomizerSettingsOTAWP = CustomizerSettingsOTAWP(),
|
|
260
281
|
m365: CustomizerSettingsM365 = CustomizerSettingsM365(),
|
|
282
|
+
core_share: CustomizerSettingsCoreShare = CustomizerSettingsCoreShare(),
|
|
261
283
|
aviator: CustomizerSettingsAviator = CustomizerSettingsAviator(),
|
|
262
284
|
):
|
|
263
285
|
self.settings = settings
|
|
@@ -286,6 +308,9 @@ class Customizer:
|
|
|
286
308
|
# Microsoft 365 Environment variables:
|
|
287
309
|
self.m365_settings = m365
|
|
288
310
|
|
|
311
|
+
# Core Share Environment variables:
|
|
312
|
+
self.core_share_settings = core_share
|
|
313
|
+
|
|
289
314
|
# Aviator variables:
|
|
290
315
|
self.aviator_settings = aviator
|
|
291
316
|
|
|
@@ -299,15 +324,18 @@ class Customizer:
|
|
|
299
324
|
self.otiv_object: OTIV | None = None
|
|
300
325
|
self.k8s_object: K8s | None = None
|
|
301
326
|
self.m365_object: M365 | None = None
|
|
327
|
+
self.core_share_object: CoreShare | None = None
|
|
302
328
|
self.browser_automation_object: BrowserAutomation | None = None
|
|
303
329
|
|
|
304
|
-
|
|
330
|
+
# end initializer
|
|
331
|
+
|
|
332
|
+
def log_header(self, text: str, char: str = "=", length: int = 80):
|
|
305
333
|
"""Helper method to output a section header in the log file
|
|
306
334
|
|
|
307
335
|
Args:
|
|
308
|
-
text (str):
|
|
336
|
+
text (str): Headline text to output into the log file.
|
|
309
337
|
char (str, optional): header line character. Defaults to "=".
|
|
310
|
-
length (int, optional): maxium length. Defaults to
|
|
338
|
+
length (int, optional): maxium length. Defaults to 80.
|
|
311
339
|
Returns:
|
|
312
340
|
None
|
|
313
341
|
"""
|
|
@@ -329,7 +357,7 @@ class Customizer:
|
|
|
329
357
|
"%s %s %s", char * char_count, text, char * (char_count + extra_char)
|
|
330
358
|
)
|
|
331
359
|
|
|
332
|
-
|
|
360
|
+
# end method definition
|
|
333
361
|
|
|
334
362
|
def init_m365(self) -> M365:
|
|
335
363
|
"""Initialize the M365 object we use to talk to the Microsoft Graph API.
|
|
@@ -373,9 +401,13 @@ class Customizer:
|
|
|
373
401
|
"Microsoft 365 Default License SKU = %s", self.m365_settings.sku_id
|
|
374
402
|
)
|
|
375
403
|
logger.info(
|
|
376
|
-
"Microsoft 365 Teams App
|
|
404
|
+
"Microsoft 365 Teams App Name = %s",
|
|
377
405
|
self.m365_settings.teams_app_name,
|
|
378
406
|
)
|
|
407
|
+
logger.info(
|
|
408
|
+
"Microsoft 365 Teams App External ID = %s",
|
|
409
|
+
self.m365_settings.teams_app_external_id,
|
|
410
|
+
)
|
|
379
411
|
|
|
380
412
|
m365_object = M365(
|
|
381
413
|
tenant_id=self.m365_settings.tenant_id,
|
|
@@ -384,16 +416,265 @@ class Customizer:
|
|
|
384
416
|
domain=self.m365_settings.domain,
|
|
385
417
|
sku_id=self.m365_settings.sku_id,
|
|
386
418
|
teams_app_name=self.m365_settings.teams_app_name,
|
|
419
|
+
teams_app_external_id=self.m365_settings.teams_app_external_id,
|
|
387
420
|
)
|
|
388
421
|
|
|
389
422
|
if m365_object and m365_object.authenticate():
|
|
390
423
|
logger.info("Connected to Microsoft Graph API.")
|
|
391
|
-
return m365_object
|
|
392
424
|
else:
|
|
393
425
|
logger.error("Failed to connect to Microsoft Graph API.")
|
|
394
426
|
return m365_object
|
|
395
427
|
|
|
396
|
-
|
|
428
|
+
logger.info(
|
|
429
|
+
"Download M365 Teams App -> '%s' (external ID = %s) from Extended ECM (OTCS)...",
|
|
430
|
+
self.m365_settings.teams_app_name,
|
|
431
|
+
self.m365_settings.teams_app_external_id,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
# Download MS Teams App from OTCS (this has with 23.2 a nasty side-effect
|
|
435
|
+
# of unsetting 2 checkboxes on that config page - we reset these checkboxes
|
|
436
|
+
# with the settings file "O365Settings.xml"):
|
|
437
|
+
response = self.otcs_frontend_object.download_config_file(
|
|
438
|
+
"/cs/cs?func=officegroups.DownloadTeamsPackage",
|
|
439
|
+
"/tmp/ot.xecm.teams.zip",
|
|
440
|
+
)
|
|
441
|
+
# this app upload will be done with the user credentials - this is required:
|
|
442
|
+
m365_object.authenticate_user(
|
|
443
|
+
self.m365_settings.user, self.m365_settings.password
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Check if the app is already installed in the apps catalog
|
|
447
|
+
# ideally we want to use the
|
|
448
|
+
app_exist = False
|
|
449
|
+
|
|
450
|
+
# If the App External ID is provided via Env variable then we
|
|
451
|
+
# prefer to use it instead of the App name:
|
|
452
|
+
if self.m365_settings.teams_app_external_id:
|
|
453
|
+
logger.info(
|
|
454
|
+
"Check if M365 Teams App -> '%s' (%s) is already installed in catalog using external app ID...",
|
|
455
|
+
self.m365_settings.teams_app_name,
|
|
456
|
+
self.m365_settings.teams_app_external_id,
|
|
457
|
+
)
|
|
458
|
+
response = m365_object.get_teams_apps(
|
|
459
|
+
filter_expression="externalId eq '{}'".format(
|
|
460
|
+
self.m365_settings.teams_app_external_id
|
|
461
|
+
)
|
|
462
|
+
)
|
|
463
|
+
# this should always be True as ID is unique:
|
|
464
|
+
app_exist = m365_object.exist_result_item(
|
|
465
|
+
response=response,
|
|
466
|
+
key="externalId",
|
|
467
|
+
value=self.m365_settings.teams_app_external_id,
|
|
468
|
+
)
|
|
469
|
+
# If the app could not be found via the external ID we fall back to
|
|
470
|
+
# search for the app by name:
|
|
471
|
+
if not app_exist:
|
|
472
|
+
if self.m365_settings.teams_app_external_id:
|
|
473
|
+
logger.info(
|
|
474
|
+
"Could not find M365 Teams App using the external ID -> %s. Try to lookup the app by name -> '%s' instead...",
|
|
475
|
+
self.m365_settings.teams_app_external_id,
|
|
476
|
+
self.m365_settings.teams_app_name,
|
|
477
|
+
)
|
|
478
|
+
logger.info(
|
|
479
|
+
"Check if M365 Teams App -> '%s' is already installed in catalog (using app name)...",
|
|
480
|
+
self.m365_settings.teams_app_name,
|
|
481
|
+
)
|
|
482
|
+
response = m365_object.get_teams_apps(
|
|
483
|
+
filter_expression="contains(displayName, '{}')".format(
|
|
484
|
+
self.m365_settings.teams_app_name
|
|
485
|
+
)
|
|
486
|
+
)
|
|
487
|
+
app_exist = m365_object.exist_result_item(
|
|
488
|
+
response=response,
|
|
489
|
+
key="displayName",
|
|
490
|
+
value=self.m365_settings.teams_app_name,
|
|
491
|
+
)
|
|
492
|
+
if app_exist:
|
|
493
|
+
# We double check that we have the effective name of the app
|
|
494
|
+
# in the catalog to avoid errors when the app is looked up
|
|
495
|
+
# by its wrong name in the customizer automation. This can
|
|
496
|
+
# happen if the app is installed manually or the environment
|
|
497
|
+
# variable is set to a wrong name.
|
|
498
|
+
app_catalog_name = m365_object.get_result_value(response, "displayName")
|
|
499
|
+
if app_catalog_name != self.m365_settings.teams_app_name:
|
|
500
|
+
logger.warning(
|
|
501
|
+
"The Extended ECM app name -> '%s' in the M365 Teams catalog does not match the defined app name '%s'! Somebody must have manually installed the app with the wrong name!",
|
|
502
|
+
app_catalog_name,
|
|
503
|
+
self.m365_settings.teams_app_name,
|
|
504
|
+
)
|
|
505
|
+
# Align the name in the settings dict with the existing name in the catalog.
|
|
506
|
+
self.m365_settings.teams_app_name = app_catalog_name
|
|
507
|
+
# Align the name in the M365 object config dict with the existing name in the catalog.
|
|
508
|
+
m365_object.config()["teamsAppName"] = app_catalog_name
|
|
509
|
+
app_internal_id = m365_object.get_result_value(
|
|
510
|
+
response=response, key="id", index=0
|
|
511
|
+
) # 0 = Index = first item
|
|
512
|
+
# Store the internal ID for later use
|
|
513
|
+
m365_object.config()["teamsAppInternalId"] = app_internal_id
|
|
514
|
+
app_catalog_version = m365_object.get_result_value(
|
|
515
|
+
response=response,
|
|
516
|
+
key="version",
|
|
517
|
+
index=0,
|
|
518
|
+
sub_dict_name="appDefinitions",
|
|
519
|
+
)
|
|
520
|
+
logger.info(
|
|
521
|
+
"M365 Teams App -> '%s' (external ID = %s) is already in app catalog with app internal ID -> %s and version -> %s. Check if we have a newer version to upload...",
|
|
522
|
+
self.m365_settings.teams_app_name,
|
|
523
|
+
self.m365_settings.teams_app_external_id,
|
|
524
|
+
app_internal_id,
|
|
525
|
+
app_catalog_version,
|
|
526
|
+
)
|
|
527
|
+
app_download_version = m365_object.extract_version_from_app_manifest(
|
|
528
|
+
app_path="/tmp/ot.xecm.teams.zip"
|
|
529
|
+
)
|
|
530
|
+
if app_catalog_version < app_download_version:
|
|
531
|
+
logger.info(
|
|
532
|
+
"Upgrading Extended ECM Teams App in catalog from version -> %s to version -> %s...",
|
|
533
|
+
app_catalog_version,
|
|
534
|
+
app_download_version,
|
|
535
|
+
)
|
|
536
|
+
response = m365_object.upload_teams_app(
|
|
537
|
+
app_path="/tmp/ot.xecm.teams.zip",
|
|
538
|
+
update_existing_app=True,
|
|
539
|
+
app_catalog_id=app_internal_id,
|
|
540
|
+
)
|
|
541
|
+
app_internal_id = m365_object.get_result_value(
|
|
542
|
+
response=response,
|
|
543
|
+
key="teamsAppId",
|
|
544
|
+
)
|
|
545
|
+
if app_internal_id:
|
|
546
|
+
logger.info(
|
|
547
|
+
"Successfully upgraded Extended ECM Teams App -> %s (external ID = %s). Internal App ID -> %s",
|
|
548
|
+
self.m365_settings.teams_app_name,
|
|
549
|
+
self.m365_settings.teams_app_external_id,
|
|
550
|
+
app_internal_id,
|
|
551
|
+
)
|
|
552
|
+
# Store the internal ID for later use
|
|
553
|
+
m365_object.config()["teamsAppInternalId"] = app_internal_id
|
|
554
|
+
else:
|
|
555
|
+
logger.error(
|
|
556
|
+
"Failed to upgrade Extended ECM Teams App -> %s (external ID = %s).",
|
|
557
|
+
self.m365_settings.teams_app_name,
|
|
558
|
+
self.m365_settings.teams_app_external_id,
|
|
559
|
+
)
|
|
560
|
+
else:
|
|
561
|
+
logger.info(
|
|
562
|
+
"No upgrade required. The downloaded version -> %s is not newer than the version -> %s which is already in the M365 app catalog.",
|
|
563
|
+
app_download_version,
|
|
564
|
+
app_catalog_version,
|
|
565
|
+
)
|
|
566
|
+
else: # Extended ECM M365 Teams app is not yet installed...
|
|
567
|
+
logger.info(
|
|
568
|
+
"Extended Teams ECM App -> '%s' (external ID = %s) is not yet in app catalog. Installing as new app...",
|
|
569
|
+
self.m365_settings.teams_app_name,
|
|
570
|
+
self.m365_settings.teams_app_external_id,
|
|
571
|
+
)
|
|
572
|
+
response = m365_object.upload_teams_app(
|
|
573
|
+
app_path="/tmp/ot.xecm.teams.zip", update_existing_app=False
|
|
574
|
+
)
|
|
575
|
+
app_internal_id = m365_object.get_result_value(
|
|
576
|
+
response=response,
|
|
577
|
+
key="id", # for new installs it is NOT "teamsAppId" but "id" as we use a different M365 Graph API endpoint !!!
|
|
578
|
+
)
|
|
579
|
+
if app_internal_id:
|
|
580
|
+
logger.info(
|
|
581
|
+
"Successfully installed Extended ECM Teams App -> '%s' (external ID = %s). Internal App ID -> %s",
|
|
582
|
+
self.m365_settings.teams_app_name,
|
|
583
|
+
self.m365_settings.teams_app_external_id,
|
|
584
|
+
app_internal_id,
|
|
585
|
+
)
|
|
586
|
+
# Store the internal ID for later use
|
|
587
|
+
m365_object.config()["teamsAppInternalId"] = app_internal_id
|
|
588
|
+
else:
|
|
589
|
+
logger.error(
|
|
590
|
+
"Failed to install Extended ECM Teams App -> '%s' (external ID = %s).",
|
|
591
|
+
self.m365_settings.teams_app_name,
|
|
592
|
+
self.m365_settings.teams_app_external_id,
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
# logger.info("======== Upload Outlook Add-In ============")
|
|
596
|
+
|
|
597
|
+
# # Download MS Outlook Add-In from OTCS:
|
|
598
|
+
# MANIFEST_FILE = "/tmp/BusinessWorkspace.Manifest.xml"
|
|
599
|
+
# if not self.otcs_frontend_object.download_config_file(
|
|
600
|
+
# "/cs/cs?func=outlookaddin.DownloadManifest",
|
|
601
|
+
# MANIFEST_FILE,
|
|
602
|
+
# "DeployedContentServer",
|
|
603
|
+
# self.otcs_settings.public_url,
|
|
604
|
+
# ):
|
|
605
|
+
# logger.error("Failed to download M365 Outlook Add-In from Extended ECM!")
|
|
606
|
+
# else:
|
|
607
|
+
# # THIS IS NOT IMPLEMENTED DUE TO LACK OF M365 GRAPH API SUPPORT!
|
|
608
|
+
# # Do it manually for now: https://admin.microsoft.com/#/Settings/IntegratedApps
|
|
609
|
+
# logger.info("Successfully downloaded M365 Outlook Add-In from Extended ECM to %s", MANIFEST_FILE)
|
|
610
|
+
# m365_object.upload_outlook_app(MANIFEST_FILE)
|
|
611
|
+
|
|
612
|
+
return m365_object
|
|
613
|
+
|
|
614
|
+
# end method definition
|
|
615
|
+
|
|
616
|
+
def init_coreshare(self) -> M365:
|
|
617
|
+
"""Initialize the Core Share object we use to talk to the Core Share API.
|
|
618
|
+
|
|
619
|
+
Args:
|
|
620
|
+
None
|
|
621
|
+
Returns:
|
|
622
|
+
object: CoreShare object or None if the object couldn't be created or
|
|
623
|
+
the authentication fails.
|
|
624
|
+
"""
|
|
625
|
+
|
|
626
|
+
logger.info(
|
|
627
|
+
"Core Share Base URL = %s", self.core_share_settings.base_url
|
|
628
|
+
)
|
|
629
|
+
logger.info(
|
|
630
|
+
"Core Share SSO URL = %s", self.core_share_settings.sso_url
|
|
631
|
+
)
|
|
632
|
+
logger.info(
|
|
633
|
+
"Core Share Client ID = %s", self.core_share_settings.client_id
|
|
634
|
+
)
|
|
635
|
+
logger.debug(
|
|
636
|
+
"Core Share Client Secret = %s",
|
|
637
|
+
self.core_share_settings.client_secret,
|
|
638
|
+
)
|
|
639
|
+
logger.info(
|
|
640
|
+
"Core Share User = %s",
|
|
641
|
+
(
|
|
642
|
+
self.core_share_settings.username
|
|
643
|
+
if self.core_share_settings.username != ""
|
|
644
|
+
else "<not configured>"
|
|
645
|
+
),
|
|
646
|
+
)
|
|
647
|
+
logger.debug(
|
|
648
|
+
"Core Share Password = %s",
|
|
649
|
+
(
|
|
650
|
+
self.core_share_settings.password
|
|
651
|
+
if self.core_share_settings.password != ""
|
|
652
|
+
else "<not configured>"
|
|
653
|
+
),
|
|
654
|
+
)
|
|
655
|
+
|
|
656
|
+
core_share_object = CoreShare(
|
|
657
|
+
base_url=self.core_share_settings.base_url,
|
|
658
|
+
sso_url=self.core_share_settings.sso_url,
|
|
659
|
+
client_id=self.core_share_settings.client_id,
|
|
660
|
+
client_secret=self.core_share_settings.client_secret,
|
|
661
|
+
username=self.core_share_settings.username,
|
|
662
|
+
password=self.core_share_settings.password,
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
if core_share_object and core_share_object.authenticate_admin():
|
|
666
|
+
logger.info("Connected to Core Share as Tenant Admin.")
|
|
667
|
+
else:
|
|
668
|
+
logger.error("Failed to connect to Core Share as Tenant Admin.")
|
|
669
|
+
|
|
670
|
+
if core_share_object and core_share_object.authenticate_user():
|
|
671
|
+
logger.info("Connected to Core Share as Tenant Service User.")
|
|
672
|
+
else:
|
|
673
|
+
logger.error("Failed to connect to Core Share as Tenant Service User.")
|
|
674
|
+
|
|
675
|
+
return core_share_object
|
|
676
|
+
|
|
677
|
+
# end method definition
|
|
397
678
|
|
|
398
679
|
def init_k8s(self) -> K8s:
|
|
399
680
|
"""Initialize the Kubernetes object we use to talk to the Kubernetes API.
|
|
@@ -407,10 +688,10 @@ class Customizer:
|
|
|
407
688
|
"""
|
|
408
689
|
|
|
409
690
|
logger.info("Connection parameters Kubernetes (K8s):")
|
|
410
|
-
logger.info("K8s inCluster
|
|
411
|
-
logger.info("K8s namespace
|
|
691
|
+
logger.info("K8s inCluster = %s", self.k8s_settings.in_cluster)
|
|
692
|
+
logger.info("K8s namespace = %s", self.k8s_settings.namespace)
|
|
412
693
|
logger.info(
|
|
413
|
-
"K8s kubeconfig file
|
|
694
|
+
"K8s kubeconfig file = %s",
|
|
414
695
|
self.k8s_settings.kubeconfig_file,
|
|
415
696
|
)
|
|
416
697
|
|
|
@@ -430,14 +711,14 @@ class Customizer:
|
|
|
430
711
|
)
|
|
431
712
|
if not otcs_frontend_scale:
|
|
432
713
|
logger.error(
|
|
433
|
-
"Cannot find Kubernetes Stateful Set for OTCS Frontends
|
|
714
|
+
"Cannot find Kubernetes Stateful Set -> '%s' for OTCS Frontends!",
|
|
434
715
|
self.otcs_settings.k8s_statefulset_frontend,
|
|
435
716
|
)
|
|
436
717
|
sys.exit()
|
|
437
718
|
|
|
438
719
|
self.otcs_settings.replicas_frontend = otcs_frontend_scale.spec.replicas # type: ignore
|
|
439
720
|
logger.info(
|
|
440
|
-
"Stateful Set -> %s has -> %s replicas",
|
|
721
|
+
"Stateful Set -> '%s' has -> %s replicas",
|
|
441
722
|
self.otcs_settings.k8s_statefulset_frontend,
|
|
442
723
|
self.otcs_settings.replicas_frontend,
|
|
443
724
|
)
|
|
@@ -448,21 +729,21 @@ class Customizer:
|
|
|
448
729
|
)
|
|
449
730
|
if not otcs_backend_scale:
|
|
450
731
|
logger.error(
|
|
451
|
-
"Cannot find Kubernetes Stateful Set for OTCS Backends
|
|
732
|
+
"Cannot find Kubernetes Stateful Set -> '%s' for OTCS Backends!",
|
|
452
733
|
self.otcs_settings.k8s_statefulset_backend,
|
|
453
734
|
)
|
|
454
735
|
sys.exit()
|
|
455
736
|
|
|
456
737
|
self.otcs_settings.replicas_backend = otcs_backend_scale.spec.replicas # type: ignore
|
|
457
738
|
logger.info(
|
|
458
|
-
"Stateful Set -> %s has -> %s replicas",
|
|
739
|
+
"Stateful Set -> '%s' has -> %s replicas",
|
|
459
740
|
self.otcs_settings.k8s_statefulset_backend,
|
|
460
741
|
self.otcs_settings.replicas_backend,
|
|
461
742
|
)
|
|
462
743
|
|
|
463
744
|
return k8s_object
|
|
464
745
|
|
|
465
|
-
|
|
746
|
+
# end method definition
|
|
466
747
|
|
|
467
748
|
def init_otds(self) -> OTDS:
|
|
468
749
|
"""Initialize the OTDS object and parameters and authenticate at OTDS once it is ready.
|
|
@@ -519,7 +800,7 @@ class Customizer:
|
|
|
519
800
|
|
|
520
801
|
return otds_object
|
|
521
802
|
|
|
522
|
-
|
|
803
|
+
# end method definition
|
|
523
804
|
|
|
524
805
|
def init_otac(self) -> OTAC:
|
|
525
806
|
"""Initialize the OTAC object and parameters.
|
|
@@ -557,6 +838,16 @@ class Customizer:
|
|
|
557
838
|
self.otds_settings.password,
|
|
558
839
|
)
|
|
559
840
|
|
|
841
|
+
# This is a work-around as OTCS container automation is not
|
|
842
|
+
# enabling the certificate reliable.
|
|
843
|
+
response = otac_object.enable_certificate(
|
|
844
|
+
cert_name="SP_otcs-admin-0", cert_type="ARC"
|
|
845
|
+
)
|
|
846
|
+
if not response:
|
|
847
|
+
logger.error("Failed to enable OTAC certificate for Extended ECM!")
|
|
848
|
+
else:
|
|
849
|
+
logger.info("Successfully enabled OTAC certificate for Extended ECM!")
|
|
850
|
+
|
|
560
851
|
# is there a known server configured for Archive Center (to sync content with)
|
|
561
852
|
if otac_object and self.otac_settings.known_server != "":
|
|
562
853
|
# wait until the OTAC pod is in ready state
|
|
@@ -587,7 +878,7 @@ class Customizer:
|
|
|
587
878
|
|
|
588
879
|
return otac_object
|
|
589
880
|
|
|
590
|
-
|
|
881
|
+
# end method definition
|
|
591
882
|
|
|
592
883
|
def init_otcs(
|
|
593
884
|
self,
|
|
@@ -646,6 +937,7 @@ class Customizer:
|
|
|
646
937
|
partition_name,
|
|
647
938
|
resource_name,
|
|
648
939
|
otds_ticket=otds_ticket,
|
|
940
|
+
base_path=self.otcs_settings.base_path,
|
|
649
941
|
)
|
|
650
942
|
|
|
651
943
|
# It is important to wait for OTCS to be configured - otherwise we
|
|
@@ -666,17 +958,17 @@ class Customizer:
|
|
|
666
958
|
otcs_cookie = otcs_object.authenticate()
|
|
667
959
|
logger.info("OTCS is ready now.")
|
|
668
960
|
|
|
669
|
-
if self.otcs_settings.update_admin_user:
|
|
670
|
-
|
|
671
|
-
otcs_object.update_user(1000, field="first_name", value="Terrarium")
|
|
672
|
-
otcs_object.update_user(1000, field="last_name", value="Admin")
|
|
961
|
+
# if self.otcs_settings.update_admin_user:
|
|
962
|
+
# Set first name and last name of Admin user (ID = 1000):
|
|
963
|
+
# otcs_object.update_user(1000, field="first_name", value="Terrarium")
|
|
964
|
+
# otcs_object.update_user(1000, field="last_name", value="Admin")
|
|
673
965
|
|
|
674
966
|
if "OTCS_RESSOURCE_ID" not in self.settings.placeholder_values:
|
|
675
|
-
self.settings.placeholder_values[
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
967
|
+
self.settings.placeholder_values["OTCS_RESSOURCE_ID"] = (
|
|
968
|
+
self.otds_object.get_resource(self.otcs_settings.resource_name)[
|
|
969
|
+
"resourceID"
|
|
970
|
+
]
|
|
971
|
+
)
|
|
680
972
|
logger.debug(
|
|
681
973
|
"Placeholder values after OTCS init = %s",
|
|
682
974
|
self.settings.placeholder_values,
|
|
@@ -686,9 +978,9 @@ class Customizer:
|
|
|
686
978
|
otcs_resource = self.otds_object.get_resource(
|
|
687
979
|
self.otcs_settings.resource_name
|
|
688
980
|
)
|
|
689
|
-
otcs_resource[
|
|
690
|
-
"
|
|
691
|
-
|
|
981
|
+
otcs_resource["logoutURL"] = (
|
|
982
|
+
f"{self.otawp_settings.public_protocol}://{self.otawp_settings.public_url}/home/system/wcp/sso/sso_logout.htm"
|
|
983
|
+
)
|
|
692
984
|
otcs_resource["logoutMethod"] = "GET"
|
|
693
985
|
|
|
694
986
|
self.otds_object.update_resource(name="cs", resource=otcs_resource)
|
|
@@ -698,7 +990,7 @@ class Customizer:
|
|
|
698
990
|
|
|
699
991
|
return otcs_object
|
|
700
992
|
|
|
701
|
-
|
|
993
|
+
# end method definition
|
|
702
994
|
|
|
703
995
|
def init_otiv(self) -> OTIV | None:
|
|
704
996
|
"""Initialize the OTIV (Intelligent Viewing) object and its OTDS settings.
|
|
@@ -750,9 +1042,27 @@ class Customizer:
|
|
|
750
1042
|
)
|
|
751
1043
|
return None
|
|
752
1044
|
|
|
1045
|
+
# Workaround for VAT-4580 (24.2.0)
|
|
1046
|
+
update_publisher = self.otds_object.update_user(
|
|
1047
|
+
partition="Content Server Service Users",
|
|
1048
|
+
user_id="iv-publisher",
|
|
1049
|
+
attribute_name="oTType",
|
|
1050
|
+
attribute_value="ServiceUser",
|
|
1051
|
+
)
|
|
1052
|
+
while update_publisher is None:
|
|
1053
|
+
update_publisher = self.otds_object.update_user(
|
|
1054
|
+
partition="Content Server Service Users",
|
|
1055
|
+
user_id="iv-publisher",
|
|
1056
|
+
attribute_name="oTType",
|
|
1057
|
+
attribute_value="ServiceUser",
|
|
1058
|
+
)
|
|
1059
|
+
time.sleep(30)
|
|
1060
|
+
|
|
1061
|
+
logger.info("OTDS user iv-publisher -> updating oTType=ServiceUser")
|
|
1062
|
+
|
|
753
1063
|
return otiv_object
|
|
754
1064
|
|
|
755
|
-
|
|
1065
|
+
# end method definition
|
|
756
1066
|
|
|
757
1067
|
def init_otpd(self) -> OTPD:
|
|
758
1068
|
"""Initialize the OTPD (PowerDocs) object and parameters.
|
|
@@ -829,7 +1139,7 @@ class Customizer:
|
|
|
829
1139
|
logger.info("OTAWP K8s Config Map = %s", self.otawp_settings.k8s_configmap)
|
|
830
1140
|
|
|
831
1141
|
logger.info(
|
|
832
|
-
"Wait for OTCS to create its OTDS resource with name -> %s...",
|
|
1142
|
+
"Wait for OTCS to create its OTDS resource with name -> '%s'...",
|
|
833
1143
|
self.otcs_settings.resource_name,
|
|
834
1144
|
)
|
|
835
1145
|
|
|
@@ -838,7 +1148,7 @@ class Customizer:
|
|
|
838
1148
|
otcs_resource = self.otds_object.get_resource(self.otcs_settings.resource_name)
|
|
839
1149
|
while otcs_resource is None:
|
|
840
1150
|
logger.warning(
|
|
841
|
-
"OTDS resource for Content Server with name -> %s does not exist yet. Waiting...",
|
|
1151
|
+
"OTDS resource for Content Server with name -> '%s' does not exist yet. Waiting...",
|
|
842
1152
|
self.otcs_settings.resource_name,
|
|
843
1153
|
)
|
|
844
1154
|
time.sleep(30)
|
|
@@ -1150,7 +1460,7 @@ class Customizer:
|
|
|
1150
1460
|
)
|
|
1151
1461
|
while otcs_partition is None:
|
|
1152
1462
|
logger.warning(
|
|
1153
|
-
"OTDS user partition for Content Server with name -> %s does not exist yet. Waiting...",
|
|
1463
|
+
"OTDS user partition for Content Server with name -> '%s' does not exist yet. Waiting...",
|
|
1154
1464
|
self.otcs_settings.partition,
|
|
1155
1465
|
)
|
|
1156
1466
|
|
|
@@ -1188,7 +1498,7 @@ class Customizer:
|
|
|
1188
1498
|
# check if the license file exists, otherwise skip for versions pre 24.1
|
|
1189
1499
|
if os.path.isfile(self.otawp_settings.license_file):
|
|
1190
1500
|
logger.info(
|
|
1191
|
-
"OTAWP license file
|
|
1501
|
+
"Found OTAWP license file -> '%s', assiging it to ressource '%s'...",
|
|
1192
1502
|
self.otawp_settings.license_file,
|
|
1193
1503
|
self.otawp_settings.resource_name,
|
|
1194
1504
|
)
|
|
@@ -1201,12 +1511,20 @@ class Customizer:
|
|
|
1201
1511
|
)
|
|
1202
1512
|
if not otawp_license:
|
|
1203
1513
|
logger.error(
|
|
1204
|
-
"Couldn't apply license -> %s for product -> %s
|
|
1514
|
+
"Couldn't apply license -> '%s' for product -> '%s' to OTDS resource -> '%s'",
|
|
1515
|
+
self.otawp_settings.license_file,
|
|
1516
|
+
self.otawp_settings.product_name,
|
|
1517
|
+
awp_resource["resourceID"],
|
|
1518
|
+
)
|
|
1519
|
+
else:
|
|
1520
|
+
logger.info(
|
|
1521
|
+
"Successfully applied license -> '%s' for product -> '%s' to OTDS resource -> '%s'",
|
|
1205
1522
|
self.otawp_settings.license_file,
|
|
1206
1523
|
self.otawp_settings.product_name,
|
|
1524
|
+
awp_resource["resourceID"],
|
|
1207
1525
|
)
|
|
1208
1526
|
|
|
1209
|
-
# Assign license to Content Server Members Partiton and otds.admin
|
|
1527
|
+
# Assign AppWorks license to Content Server Members Partiton and otds.admin:
|
|
1210
1528
|
for partition_name in ["otds.admin", self.otcs_settings.partition]:
|
|
1211
1529
|
if self.otds_object.is_partition_licensed(
|
|
1212
1530
|
partition_name=partition_name,
|
|
@@ -1229,13 +1547,20 @@ class Customizer:
|
|
|
1229
1547
|
)
|
|
1230
1548
|
if not assigned_license:
|
|
1231
1549
|
logger.error(
|
|
1232
|
-
"Partition -> %s could not be assigned to license -> %s (%s)",
|
|
1550
|
+
"Partition -> '%s' could not be assigned to license -> '%s' (%s)",
|
|
1551
|
+
partition_name,
|
|
1552
|
+
self.otawp_settings.product_name,
|
|
1553
|
+
"USERS",
|
|
1554
|
+
)
|
|
1555
|
+
else:
|
|
1556
|
+
logger.info(
|
|
1557
|
+
"Partition -> '%s' successfully assigned to license -> '%s' (%s)",
|
|
1233
1558
|
partition_name,
|
|
1234
1559
|
self.otawp_settings.product_name,
|
|
1235
1560
|
"USERS",
|
|
1236
1561
|
)
|
|
1237
1562
|
|
|
1238
|
-
|
|
1563
|
+
# end method definition
|
|
1239
1564
|
|
|
1240
1565
|
def restart_otcs_service(self, otcs_object: OTCS, extra_wait_time: int = 60):
|
|
1241
1566
|
"""Restart the Content Server service in all OTCS pods
|
|
@@ -1258,11 +1583,11 @@ class Customizer:
|
|
|
1258
1583
|
for x in range(0, self.otcs_settings.replicas_frontend):
|
|
1259
1584
|
pod_name = self.otcs_settings.k8s_statefulset_frontend + "-" + str(x)
|
|
1260
1585
|
|
|
1261
|
-
logger.info("Deactivate Liveness probe for pod -> %s", pod_name)
|
|
1586
|
+
logger.info("Deactivate Liveness probe for pod -> '%s'", pod_name)
|
|
1262
1587
|
self.k8s_object.exec_pod_command(
|
|
1263
1588
|
pod_name, ["/bin/sh", "-c", "touch /tmp/keepalive"]
|
|
1264
1589
|
)
|
|
1265
|
-
logger.info("Restarting pod -> %s", pod_name)
|
|
1590
|
+
logger.info("Restarting pod -> '%s'", pod_name)
|
|
1266
1591
|
self.k8s_object.exec_pod_command(
|
|
1267
1592
|
pod_name, ["/bin/sh", "-c", "/opt/opentext/cs/stop_csserver"]
|
|
1268
1593
|
)
|
|
@@ -1274,11 +1599,11 @@ class Customizer:
|
|
|
1274
1599
|
for x in range(0, self.otcs_settings.replicas_backend):
|
|
1275
1600
|
pod_name = self.otcs_settings.k8s_statefulset_backend + "-" + str(x)
|
|
1276
1601
|
|
|
1277
|
-
logger.info("Deactivate Liveness probe for pod -> %s", pod_name)
|
|
1602
|
+
logger.info("Deactivate Liveness probe for pod -> '%s'", pod_name)
|
|
1278
1603
|
self.k8s_object.exec_pod_command(
|
|
1279
1604
|
pod_name, ["/bin/sh", "-c", "touch /tmp/keepalive"]
|
|
1280
1605
|
)
|
|
1281
|
-
logger.info("Restarting pod -> %s", pod_name)
|
|
1606
|
+
logger.info("Restarting pod -> '%s'", pod_name)
|
|
1282
1607
|
self.k8s_object.exec_pod_command(
|
|
1283
1608
|
pod_name, ["/bin/sh", "-c", "/opt/opentext/cs/stop_csserver"]
|
|
1284
1609
|
)
|
|
@@ -1298,7 +1623,7 @@ class Customizer:
|
|
|
1298
1623
|
for x in range(0, self.otcs_settings.replicas_frontend):
|
|
1299
1624
|
pod_name = self.otcs_settings.k8s_statefulset_frontend + "-" + str(x)
|
|
1300
1625
|
|
|
1301
|
-
logger.info("Reactivate Liveness probe for pod -> %s", pod_name)
|
|
1626
|
+
logger.info("Reactivate Liveness probe for pod -> '%s'", pod_name)
|
|
1302
1627
|
self.k8s_object.exec_pod_command(
|
|
1303
1628
|
pod_name, ["/bin/sh", "-c", "rm /tmp/keepalive"]
|
|
1304
1629
|
)
|
|
@@ -1306,7 +1631,7 @@ class Customizer:
|
|
|
1306
1631
|
for x in range(0, self.otcs_settings.replicas_backend):
|
|
1307
1632
|
pod_name = self.otcs_settings.k8s_statefulset_backend + "-" + str(x)
|
|
1308
1633
|
|
|
1309
|
-
logger.info("Reactivate Liveness probe for pod -> %s", pod_name)
|
|
1634
|
+
logger.info("Reactivate Liveness probe for pod -> '%s'", pod_name)
|
|
1310
1635
|
self.k8s_object.exec_pod_command(
|
|
1311
1636
|
pod_name, ["/bin/sh", "-c", "rm /tmp/keepalive"]
|
|
1312
1637
|
)
|
|
@@ -1322,7 +1647,7 @@ class Customizer:
|
|
|
1322
1647
|
time.sleep(extra_wait_time)
|
|
1323
1648
|
logger.info("Continue customizing...")
|
|
1324
1649
|
|
|
1325
|
-
|
|
1650
|
+
# end method definition
|
|
1326
1651
|
|
|
1327
1652
|
def restart_otac_service(self) -> bool:
|
|
1328
1653
|
"""Restart the Archive Center spawner service in OTAC pod
|
|
@@ -1337,7 +1662,7 @@ class Customizer:
|
|
|
1337
1662
|
return False
|
|
1338
1663
|
|
|
1339
1664
|
logger.info(
|
|
1340
|
-
"Restarting spawner service in Archive Center pod -> %s",
|
|
1665
|
+
"Restarting spawner service in Archive Center pod -> '%s'",
|
|
1341
1666
|
self.otac_settings.k8s_pod_name,
|
|
1342
1667
|
)
|
|
1343
1668
|
# The Archive Center Spawner needs to be run in "interactive" mode - otherwise the command will "hang":
|
|
@@ -1355,7 +1680,7 @@ class Customizer:
|
|
|
1355
1680
|
else:
|
|
1356
1681
|
return False
|
|
1357
1682
|
|
|
1358
|
-
|
|
1683
|
+
# end method definition
|
|
1359
1684
|
|
|
1360
1685
|
def restart_otawp_pod(self):
|
|
1361
1686
|
"""Delete the AppWorks Platform Pod to make Kubernetes restart it.
|
|
@@ -1367,7 +1692,7 @@ class Customizer:
|
|
|
1367
1692
|
|
|
1368
1693
|
self.k8s_object.delete_pod(self.otawp_settings.k8s_statefulset + "-0")
|
|
1369
1694
|
|
|
1370
|
-
|
|
1695
|
+
# end method definition
|
|
1371
1696
|
|
|
1372
1697
|
def consolidate_otds(self):
|
|
1373
1698
|
"""Consolidate OTDS resources
|
|
@@ -1380,7 +1705,7 @@ class Customizer:
|
|
|
1380
1705
|
if self.otawp_settings.enabled: # is AppWorks Platform deployed?
|
|
1381
1706
|
self.otds_object.consolidate(self.otawp_settings.resource_name)
|
|
1382
1707
|
|
|
1383
|
-
|
|
1708
|
+
# end method definition
|
|
1384
1709
|
|
|
1385
1710
|
def import_powerdocs_configuration(self, otpd_object: OTPD):
|
|
1386
1711
|
"""Import a database export (zip file) into the PowerDocs database
|
|
@@ -1393,7 +1718,7 @@ class Customizer:
|
|
|
1393
1718
|
# Download file from remote location specified by the OTPD_DBIMPORTFILE
|
|
1394
1719
|
# this must be a public place without authentication:
|
|
1395
1720
|
logger.info(
|
|
1396
|
-
"Download PowerDocs database file from URL -> %s",
|
|
1721
|
+
"Download PowerDocs database file from URL -> '%s'",
|
|
1397
1722
|
self.otpd_settings.db_importfile,
|
|
1398
1723
|
)
|
|
1399
1724
|
|
|
@@ -1401,7 +1726,7 @@ class Customizer:
|
|
|
1401
1726
|
package = requests.get(self.otpd_settings.db_importfile, timeout=60)
|
|
1402
1727
|
package.raise_for_status()
|
|
1403
1728
|
logger.info(
|
|
1404
|
-
"Successfully downloaded PowerDocs database file -> %s; status code -> %s",
|
|
1729
|
+
"Successfully downloaded PowerDocs database file -> '%s'; status code -> %s",
|
|
1405
1730
|
self.otpd_settings.db_importfile,
|
|
1406
1731
|
package.status_code,
|
|
1407
1732
|
)
|
|
@@ -1422,7 +1747,7 @@ class Customizer:
|
|
|
1422
1747
|
except requests.exceptions.HTTPError as err:
|
|
1423
1748
|
logger.error("Request error -> %s", err)
|
|
1424
1749
|
|
|
1425
|
-
|
|
1750
|
+
# end method definition
|
|
1426
1751
|
|
|
1427
1752
|
def set_maintenance_mode(self, enable: bool = True):
|
|
1428
1753
|
"""Enable or Disable Maintenance Mode
|
|
@@ -1460,12 +1785,14 @@ class Customizer:
|
|
|
1460
1785
|
)
|
|
1461
1786
|
logger.info("OTCS frontend is now back in Production Mode!")
|
|
1462
1787
|
|
|
1788
|
+
# end method definition
|
|
1789
|
+
|
|
1463
1790
|
def customization_run(self):
|
|
1464
1791
|
"""Central function to initiate the customization"""
|
|
1465
1792
|
# Set Timer for duration calculation
|
|
1466
|
-
self.settings.customizer_start_time = (
|
|
1467
|
-
|
|
1468
|
-
)
|
|
1793
|
+
self.settings.customizer_start_time = self.settings.customizer_end_time = (
|
|
1794
|
+
datetime.now()
|
|
1795
|
+
)
|
|
1469
1796
|
|
|
1470
1797
|
# Initialize the OTDS, OTCS and OTPD objects and wait for the
|
|
1471
1798
|
# pods to be ready. If any of this fails we bail out:
|
|
@@ -1548,119 +1875,40 @@ class Customizer:
|
|
|
1548
1875
|
else:
|
|
1549
1876
|
self.otpd_object = None
|
|
1550
1877
|
|
|
1878
|
+
if self.core_share_settings.enabled: # is Core Share enabled?
|
|
1879
|
+
self.log_header("Initialize Core Share")
|
|
1880
|
+
|
|
1881
|
+
self.core_share_object = self.init_coreshare()
|
|
1882
|
+
if not self.core_share_object:
|
|
1883
|
+
logger.error("Failed to initialize Core Share - exiting...")
|
|
1884
|
+
sys.exit()
|
|
1885
|
+
else:
|
|
1886
|
+
self.core_share_object = None
|
|
1887
|
+
|
|
1551
1888
|
if (
|
|
1552
1889
|
self.m365_settings.enabled
|
|
1553
1890
|
and self.m365_settings.user != ""
|
|
1554
1891
|
and self.m365_settings.password != ""
|
|
1555
1892
|
): # is M365 enabled?
|
|
1556
|
-
self.log_header("Initialize
|
|
1893
|
+
self.log_header("Initialize Microsoft 365")
|
|
1557
1894
|
|
|
1558
1895
|
# Initialize the M365 object and connection to M365 Graph API:
|
|
1559
1896
|
self.m365_object = self.init_m365()
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
# Download MS Teams App from OTCS (this has with 23.2 a nasty side-effect
|
|
1564
|
-
# of unsetting 2 checkboxes on that config page - we reset these checkboxes
|
|
1565
|
-
# with the settings file "O365Settings.xml"):
|
|
1566
|
-
response = self.otcs_frontend_object.download_config_file(
|
|
1567
|
-
"/cs/cs?func=officegroups.DownloadTeamsPackage",
|
|
1568
|
-
"/tmp/ot.xecm.teams.zip",
|
|
1569
|
-
)
|
|
1570
|
-
# this app upload will be done with the user credentials - this is required:
|
|
1571
|
-
self.m365_object.authenticate_user(
|
|
1572
|
-
self.m365_settings.user, self.m365_settings.password
|
|
1573
|
-
)
|
|
1574
|
-
|
|
1575
|
-
# Check if the app is already installed in the apps catalog:
|
|
1576
|
-
response = self.m365_object.get_teams_apps(
|
|
1577
|
-
f"contains(displayName, '{self.m365_settings.teams_app_name}')"
|
|
1578
|
-
)
|
|
1579
|
-
if self.m365_object.exist_result_item(
|
|
1580
|
-
response, "displayName", self.m365_settings.teams_app_name
|
|
1581
|
-
):
|
|
1582
|
-
app_catalog_id = self.m365_object.get_result_value(
|
|
1583
|
-
response=response, key="id", index=0
|
|
1584
|
-
) # 0 = Index = first item
|
|
1585
|
-
app_catalog_version = self.m365_object.get_result_value(
|
|
1586
|
-
response=response,
|
|
1587
|
-
key="version",
|
|
1588
|
-
index=0,
|
|
1589
|
-
sub_dict_name="appDefinitions",
|
|
1590
|
-
)
|
|
1591
|
-
logger.info(
|
|
1592
|
-
"Extended ECM Teams App is already in app catalog with app catalog ID -> %s and version -> %s. Check if we have a newer version to upload...",
|
|
1593
|
-
app_catalog_id,
|
|
1594
|
-
app_catalog_version,
|
|
1595
|
-
)
|
|
1596
|
-
app_upload_version = self.m365_object.extract_version_from_app_manifest(
|
|
1597
|
-
app_path="/tmp/ot.xecm.teams.zip"
|
|
1598
|
-
)
|
|
1599
|
-
if app_catalog_version < app_upload_version:
|
|
1600
|
-
logger.info(
|
|
1601
|
-
"Upgrading Extended ECM Teams App in catalog from version -> %s to version -> %s...",
|
|
1602
|
-
app_catalog_version,
|
|
1603
|
-
app_upload_version,
|
|
1604
|
-
)
|
|
1605
|
-
response = self.m365_object.upload_teams_app(
|
|
1606
|
-
app_path="/tmp/ot.xecm.teams.zip",
|
|
1607
|
-
update_existing_app=True,
|
|
1608
|
-
app_catalog_id=app_catalog_id,
|
|
1609
|
-
)
|
|
1610
|
-
else:
|
|
1611
|
-
logger.info(
|
|
1612
|
-
"No upgrade required. The upload version -> %s is not newer than the version -> %s which is in the M365 app catalog.",
|
|
1613
|
-
app_upload_version,
|
|
1614
|
-
app_catalog_version,
|
|
1615
|
-
)
|
|
1616
|
-
else:
|
|
1617
|
-
logger.info(
|
|
1618
|
-
"Extended Teams ECM App is not yet in app catalog. Installing as new app..."
|
|
1619
|
-
)
|
|
1620
|
-
response = self.m365_object.upload_teams_app(
|
|
1621
|
-
app_path="/tmp/ot.xecm.teams.zip"
|
|
1622
|
-
)
|
|
1623
|
-
|
|
1624
|
-
# logger.info("======== Upload Outlook Add-In ============")
|
|
1625
|
-
|
|
1626
|
-
# # Download MS Outlook Add-In from OTCS:
|
|
1627
|
-
# MANIFEST_FILE = "/tmp/BusinessWorkspace.Manifest.xml"
|
|
1628
|
-
# if not self.otcs_frontend_object.download_config_file(
|
|
1629
|
-
# "/cs/cs?func=outlookaddin.DownloadManifest",
|
|
1630
|
-
# MANIFEST_FILE,
|
|
1631
|
-
# "DeployedContentServer",
|
|
1632
|
-
# self.otcs_settings.public_url,
|
|
1633
|
-
# ):
|
|
1634
|
-
# logger.error("Failed to download M365 Outlook Add-In from Extended ECM!")
|
|
1635
|
-
# else:
|
|
1636
|
-
# # THIS IS NOT IMPLEMENTED DUE TO LACK OF M365 GRAPH API SUPPORT!
|
|
1637
|
-
# # Do it manually for now: https://admin.microsoft.com/#/Settings/IntegratedApps
|
|
1638
|
-
# logger.info("Successfully downloaded M365 Outlook Add-In from Extended ECM to %s", MANIFEST_FILE)
|
|
1639
|
-
# self.m365_object.upload_outlook_app(MANIFEST_FILE)
|
|
1640
|
-
else:
|
|
1641
|
-
self.m365_object = None
|
|
1642
|
-
|
|
1643
|
-
# self.log_header("Initialize Browser Automation...")
|
|
1644
|
-
|
|
1645
|
-
# We initialize a Selenium based browser automation for
|
|
1646
|
-
# those die-hard settings that cannot be automated via REST API
|
|
1647
|
-
# nor LLConfig nor Transport:
|
|
1648
|
-
# self.browser_automation_object = self.init_browser_automation()
|
|
1649
|
-
# if not self.browser_automation_object:
|
|
1650
|
-
# logger.error("Failed to initialize Browser Automation - exiting...")
|
|
1651
|
-
# sys.exit()
|
|
1897
|
+
if not self.m365_object:
|
|
1898
|
+
logger.error("Failed to initialize Microsoft 365!")
|
|
1899
|
+
sys.exit()
|
|
1652
1900
|
|
|
1653
1901
|
self.log_header("Processing Payload")
|
|
1654
1902
|
|
|
1655
1903
|
cust_payload_list = []
|
|
1656
1904
|
# Is uncompressed payload provided?
|
|
1657
1905
|
if os.path.exists(self.settings.cust_payload):
|
|
1658
|
-
logger.info("Found payload file -> %s", self.settings.cust_payload)
|
|
1906
|
+
logger.info("Found payload file -> '%s'", self.settings.cust_payload)
|
|
1659
1907
|
cust_payload_list.append(self.settings.cust_payload)
|
|
1660
1908
|
# Is compressed payload provided?
|
|
1661
1909
|
if os.path.exists(self.settings.cust_payload_gz):
|
|
1662
1910
|
logger.info(
|
|
1663
|
-
"Found compressed payload file -> %s", self.settings.cust_payload_gz
|
|
1911
|
+
"Found compressed payload file -> '%s'", self.settings.cust_payload_gz
|
|
1664
1912
|
)
|
|
1665
1913
|
cust_payload_list.append(self.settings.cust_payload_gz)
|
|
1666
1914
|
|
|
@@ -1668,16 +1916,16 @@ class Customizer:
|
|
|
1668
1916
|
if os.path.exists(self.settings.cust_payload_external):
|
|
1669
1917
|
for filename in os.scandir(self.settings.cust_payload_external):
|
|
1670
1918
|
if filename.is_file() and os.path.getsize(filename) > 0:
|
|
1671
|
-
logger.info("Found external payload file -> %s", filename.path)
|
|
1919
|
+
logger.info("Found external payload file -> '%s'", filename.path)
|
|
1672
1920
|
cust_payload_list.append(filename.path)
|
|
1673
1921
|
else:
|
|
1674
1922
|
logger.info(
|
|
1675
|
-
"No external payload file -> %s", self.settings.cust_payload_external
|
|
1923
|
+
"No external payload file -> '%s'", self.settings.cust_payload_external
|
|
1676
1924
|
)
|
|
1677
1925
|
|
|
1678
1926
|
for cust_payload in cust_payload_list:
|
|
1679
1927
|
# Open the payload file. If this fails we bail out:
|
|
1680
|
-
logger.info("Starting processing of payload -> %s", cust_payload)
|
|
1928
|
+
logger.info("Starting processing of payload -> '%s'", cust_payload)
|
|
1681
1929
|
|
|
1682
1930
|
# Set startTime for duration calculation
|
|
1683
1931
|
start_time = datetime.now()
|
|
@@ -1693,11 +1941,13 @@ class Customizer:
|
|
|
1693
1941
|
otcs_restart_callback=self.restart_otcs_service,
|
|
1694
1942
|
otiv_object=self.otiv_object,
|
|
1695
1943
|
m365_object=self.m365_object,
|
|
1944
|
+
core_share_object=self.core_share_object,
|
|
1696
1945
|
browser_automation_object=self.browser_automation_object,
|
|
1697
1946
|
placeholder_values=self.settings.placeholder_values, # this dict includes placeholder replacements for the Ressource IDs of OTAWP and OTCS
|
|
1698
1947
|
log_header_callback=self.log_header,
|
|
1699
1948
|
stop_on_error=self.settings.stop_on_error,
|
|
1700
1949
|
aviator_enabled=self.aviator_settings.enabled,
|
|
1950
|
+
upload_status_files=self.otcs_settings.upload_status_files,
|
|
1701
1951
|
)
|
|
1702
1952
|
# Load the payload file and initialize the payload sections:
|
|
1703
1953
|
if not payload_object.init_payload():
|
|
@@ -1713,50 +1963,53 @@ class Customizer:
|
|
|
1713
1963
|
self.consolidate_otds()
|
|
1714
1964
|
|
|
1715
1965
|
# Upload payload file for later review to Enterprise Workspace
|
|
1716
|
-
self.
|
|
1717
|
-
|
|
1718
|
-
self.
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1966
|
+
if self.otcs_settings.upload_config_files:
|
|
1967
|
+
self.log_header("Upload Payload file to Extended ECM")
|
|
1968
|
+
response = self.otcs_backend_object.get_node_from_nickname(
|
|
1969
|
+
self.settings.cust_target_folder_nickname
|
|
1970
|
+
)
|
|
1971
|
+
target_folder_id = self.otcs_backend_object.get_result_value(
|
|
1972
|
+
response, "id"
|
|
1973
|
+
)
|
|
1974
|
+
if not target_folder_id:
|
|
1975
|
+
target_folder_id = 2000 # use Enterprise Workspace as fallback
|
|
1976
|
+
# Write YAML file with upadated payload (including IDs, etc.).
|
|
1977
|
+
# We need to write to /tmp as initial location is read-only:
|
|
1978
|
+
payload_file = os.path.basename(cust_payload)
|
|
1979
|
+
payload_file = (
|
|
1980
|
+
payload_file[: -len(".gz.b64")]
|
|
1981
|
+
if payload_file.endswith(".gz.b64")
|
|
1982
|
+
else payload_file
|
|
1983
|
+
)
|
|
1984
|
+
cust_payload = "/tmp/" + payload_file
|
|
1732
1985
|
|
|
1733
|
-
|
|
1734
|
-
|
|
1986
|
+
with open(cust_payload, "w", encoding="utf-8") as file:
|
|
1987
|
+
yaml.dump(payload_object.get_payload(), file)
|
|
1735
1988
|
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
)
|
|
1742
|
-
target_document_id = self.otcs_backend_object.get_result_value(
|
|
1743
|
-
response, "id"
|
|
1744
|
-
)
|
|
1745
|
-
if target_document_id:
|
|
1746
|
-
response = self.otcs_backend_object.add_document_version(
|
|
1747
|
-
int(target_document_id),
|
|
1748
|
-
cust_payload,
|
|
1749
|
-
os.path.basename(cust_payload),
|
|
1750
|
-
"text/plain",
|
|
1751
|
-
"Updated payload file after re-run of customization",
|
|
1989
|
+
# Check if the payload file has been uploaded before.
|
|
1990
|
+
# This can happen if we re-run the python container.
|
|
1991
|
+
# In this case we add a version to the existing document:
|
|
1992
|
+
response = self.otcs_backend_object.get_node_by_parent_and_name(
|
|
1993
|
+
int(target_folder_id), os.path.basename(cust_payload)
|
|
1752
1994
|
)
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
cust_payload,
|
|
1756
|
-
os.path.basename(cust_payload),
|
|
1757
|
-
"text/plain",
|
|
1758
|
-
int(target_folder_id),
|
|
1995
|
+
target_document_id = self.otcs_backend_object.get_result_value(
|
|
1996
|
+
response, "id"
|
|
1759
1997
|
)
|
|
1998
|
+
if target_document_id:
|
|
1999
|
+
response = self.otcs_backend_object.add_document_version(
|
|
2000
|
+
int(target_document_id),
|
|
2001
|
+
cust_payload,
|
|
2002
|
+
os.path.basename(cust_payload),
|
|
2003
|
+
"text/plain",
|
|
2004
|
+
"Updated payload file after re-run of customization",
|
|
2005
|
+
)
|
|
2006
|
+
else:
|
|
2007
|
+
response = self.otcs_backend_object.upload_file_to_parent(
|
|
2008
|
+
cust_payload,
|
|
2009
|
+
os.path.basename(cust_payload),
|
|
2010
|
+
"text/plain",
|
|
2011
|
+
int(target_folder_id),
|
|
2012
|
+
)
|
|
1760
2013
|
|
|
1761
2014
|
duration = datetime.now() - start_time
|
|
1762
2015
|
self.log_header(
|
|
@@ -1804,9 +2057,11 @@ class Customizer:
|
|
|
1804
2057
|
self.otds_object.impersonate_resource(self.otawp_settings.resource_name)
|
|
1805
2058
|
|
|
1806
2059
|
# Upload log file for later review to "Deployment" folder in "Administration" folder
|
|
1807
|
-
if
|
|
2060
|
+
if (
|
|
2061
|
+
os.path.exists(self.settings.cust_log_file)
|
|
2062
|
+
and self.otcs_settings.upload_log_file
|
|
2063
|
+
):
|
|
1808
2064
|
self.log_header("Upload log file to Extended ECM")
|
|
1809
|
-
# logger.info("========== Upload log file to Extended ECM =============")
|
|
1810
2065
|
response = self.otcs_backend_object.get_node_from_nickname(
|
|
1811
2066
|
self.settings.cust_target_folder_nickname
|
|
1812
2067
|
)
|
|
@@ -1824,18 +2079,19 @@ class Customizer:
|
|
|
1824
2079
|
)
|
|
1825
2080
|
if target_document_id:
|
|
1826
2081
|
response = self.otcs_backend_object.add_document_version(
|
|
1827
|
-
int(target_document_id),
|
|
1828
|
-
self.settings.cust_log_file,
|
|
1829
|
-
os.path.basename(self.settings.cust_log_file),
|
|
1830
|
-
"text/plain",
|
|
1831
|
-
"Updated Python Log after re-run of customization",
|
|
2082
|
+
node_id=int(target_document_id),
|
|
2083
|
+
file_url=self.settings.cust_log_file,
|
|
2084
|
+
file_name=os.path.basename(self.settings.cust_log_file),
|
|
2085
|
+
mime_type="text/plain",
|
|
2086
|
+
description="Updated Python Log after re-run of customization",
|
|
1832
2087
|
)
|
|
1833
2088
|
else:
|
|
1834
2089
|
response = self.otcs_backend_object.upload_file_to_parent(
|
|
1835
|
-
self.settings.cust_log_file,
|
|
1836
|
-
os.path.basename(self.settings.cust_log_file),
|
|
1837
|
-
"text/plain",
|
|
1838
|
-
int(target_folder_id),
|
|
2090
|
+
file_url=self.settings.cust_log_file,
|
|
2091
|
+
file_name=os.path.basename(self.settings.cust_log_file),
|
|
2092
|
+
mime_type="text/plain",
|
|
2093
|
+
parent_id=int(target_folder_id),
|
|
2094
|
+
description="Initial Python Log after first run of customization",
|
|
1839
2095
|
)
|
|
1840
2096
|
|
|
1841
2097
|
self.settings.customizer_end_time = datetime.now()
|
|
@@ -1845,29 +2101,4 @@ class Customizer:
|
|
|
1845
2101
|
)
|
|
1846
2102
|
)
|
|
1847
2103
|
|
|
1848
|
-
|
|
1849
|
-
if __name__ == "__main__":
|
|
1850
|
-
logging.basicConfig(
|
|
1851
|
-
format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
|
|
1852
|
-
datefmt="%d-%b-%Y %H:%M:%S",
|
|
1853
|
-
level=logging.INFO,
|
|
1854
|
-
handlers=[
|
|
1855
|
-
logging.StreamHandler(sys.stdout),
|
|
1856
|
-
],
|
|
1857
|
-
)
|
|
1858
|
-
|
|
1859
|
-
my_customizer = Customizer(
|
|
1860
|
-
otcs=CustomizerSettingsOTCS(
|
|
1861
|
-
hostname="otcs.eng.terrarium.cloud",
|
|
1862
|
-
hostname_backend="otcs.eng.terrarium.cloud",
|
|
1863
|
-
hostname_frontend="otcs.eng.terrarium.cloud",
|
|
1864
|
-
protocol="https",
|
|
1865
|
-
port_backend=443,
|
|
1866
|
-
),
|
|
1867
|
-
otds=CustomizerSettingsOTDS(hostname="otds.eng.terrarium.cloud"),
|
|
1868
|
-
otpd=CustomizerSettingsOTPD(enabled=False),
|
|
1869
|
-
k8s=CustomizerSettingsK8S(enabled=False),
|
|
1870
|
-
otiv=CustomizerSettingsOTIV(enabled=True),
|
|
1871
|
-
)
|
|
1872
|
-
|
|
1873
|
-
my_customizer.customization_run()
|
|
2104
|
+
# end method definition
|