pyxecm 1.4__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 +4 -0
- pyxecm/customizer/browser_automation.py +164 -54
- pyxecm/customizer/customizer.py +451 -235
- pyxecm/customizer/k8s.py +6 -6
- pyxecm/customizer/m365.py +1136 -221
- pyxecm/customizer/payload.py +13163 -5844
- pyxecm/customizer/pht.py +503 -0
- pyxecm/customizer/salesforce.py +694 -114
- pyxecm/customizer/sap.py +4 -4
- pyxecm/customizer/servicenow.py +1221 -0
- pyxecm/customizer/successfactors.py +1056 -0
- pyxecm/helper/__init__.py +2 -0
- pyxecm/helper/assoc.py +24 -1
- pyxecm/helper/data.py +1527 -0
- pyxecm/helper/web.py +170 -46
- pyxecm/helper/xml.py +170 -34
- pyxecm/otac.py +309 -23
- pyxecm/otcs.py +2779 -698
- pyxecm/otds.py +347 -108
- pyxecm/otmm.py +808 -0
- pyxecm/otpd.py +13 -10
- {pyxecm-1.4.dist-info → pyxecm-1.5.dist-info}/METADATA +3 -1
- pyxecm-1.5.dist-info/RECORD +30 -0
- {pyxecm-1.4.dist-info → pyxecm-1.5.dist-info}/WHEEL +1 -1
- pyxecm-1.4.dist-info/RECORD +0 -24
- {pyxecm-1.4.dist-info → pyxecm-1.5.dist-info}/LICENSE +0 -0
- {pyxecm-1.4.dist-info → pyxecm-1.5.dist-info}/top_level.txt +0 -0
pyxecm/customizer/customizer.py
CHANGED
|
@@ -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,14 +1511,14 @@ class Customizer:
|
|
|
1201
1511
|
)
|
|
1202
1512
|
if not otawp_license:
|
|
1203
1513
|
logger.error(
|
|
1204
|
-
"Couldn't apply license -> %s for product -> %s to OTDS resource -> %s",
|
|
1514
|
+
"Couldn't apply license -> '%s' for product -> '%s' to OTDS resource -> '%s'",
|
|
1205
1515
|
self.otawp_settings.license_file,
|
|
1206
1516
|
self.otawp_settings.product_name,
|
|
1207
1517
|
awp_resource["resourceID"],
|
|
1208
1518
|
)
|
|
1209
1519
|
else:
|
|
1210
1520
|
logger.info(
|
|
1211
|
-
"Successfully applied license -> %s for product -> %s to OTDS resource -> %s",
|
|
1521
|
+
"Successfully applied license -> '%s' for product -> '%s' to OTDS resource -> '%s'",
|
|
1212
1522
|
self.otawp_settings.license_file,
|
|
1213
1523
|
self.otawp_settings.product_name,
|
|
1214
1524
|
awp_resource["resourceID"],
|
|
@@ -1237,20 +1547,20 @@ class Customizer:
|
|
|
1237
1547
|
)
|
|
1238
1548
|
if not assigned_license:
|
|
1239
1549
|
logger.error(
|
|
1240
|
-
"Partition -> %s could not be assigned to license -> %s (%s)",
|
|
1550
|
+
"Partition -> '%s' could not be assigned to license -> '%s' (%s)",
|
|
1241
1551
|
partition_name,
|
|
1242
1552
|
self.otawp_settings.product_name,
|
|
1243
1553
|
"USERS",
|
|
1244
1554
|
)
|
|
1245
1555
|
else:
|
|
1246
1556
|
logger.info(
|
|
1247
|
-
"Partition -> %s successfully assigned to license -> %s (%s)",
|
|
1557
|
+
"Partition -> '%s' successfully assigned to license -> '%s' (%s)",
|
|
1248
1558
|
partition_name,
|
|
1249
1559
|
self.otawp_settings.product_name,
|
|
1250
1560
|
"USERS",
|
|
1251
1561
|
)
|
|
1252
1562
|
|
|
1253
|
-
|
|
1563
|
+
# end method definition
|
|
1254
1564
|
|
|
1255
1565
|
def restart_otcs_service(self, otcs_object: OTCS, extra_wait_time: int = 60):
|
|
1256
1566
|
"""Restart the Content Server service in all OTCS pods
|
|
@@ -1273,11 +1583,11 @@ class Customizer:
|
|
|
1273
1583
|
for x in range(0, self.otcs_settings.replicas_frontend):
|
|
1274
1584
|
pod_name = self.otcs_settings.k8s_statefulset_frontend + "-" + str(x)
|
|
1275
1585
|
|
|
1276
|
-
logger.info("Deactivate Liveness probe for pod -> %s", pod_name)
|
|
1586
|
+
logger.info("Deactivate Liveness probe for pod -> '%s'", pod_name)
|
|
1277
1587
|
self.k8s_object.exec_pod_command(
|
|
1278
1588
|
pod_name, ["/bin/sh", "-c", "touch /tmp/keepalive"]
|
|
1279
1589
|
)
|
|
1280
|
-
logger.info("Restarting pod -> %s", pod_name)
|
|
1590
|
+
logger.info("Restarting pod -> '%s'", pod_name)
|
|
1281
1591
|
self.k8s_object.exec_pod_command(
|
|
1282
1592
|
pod_name, ["/bin/sh", "-c", "/opt/opentext/cs/stop_csserver"]
|
|
1283
1593
|
)
|
|
@@ -1289,11 +1599,11 @@ class Customizer:
|
|
|
1289
1599
|
for x in range(0, self.otcs_settings.replicas_backend):
|
|
1290
1600
|
pod_name = self.otcs_settings.k8s_statefulset_backend + "-" + str(x)
|
|
1291
1601
|
|
|
1292
|
-
logger.info("Deactivate Liveness probe for pod -> %s", pod_name)
|
|
1602
|
+
logger.info("Deactivate Liveness probe for pod -> '%s'", pod_name)
|
|
1293
1603
|
self.k8s_object.exec_pod_command(
|
|
1294
1604
|
pod_name, ["/bin/sh", "-c", "touch /tmp/keepalive"]
|
|
1295
1605
|
)
|
|
1296
|
-
logger.info("Restarting pod -> %s", pod_name)
|
|
1606
|
+
logger.info("Restarting pod -> '%s'", pod_name)
|
|
1297
1607
|
self.k8s_object.exec_pod_command(
|
|
1298
1608
|
pod_name, ["/bin/sh", "-c", "/opt/opentext/cs/stop_csserver"]
|
|
1299
1609
|
)
|
|
@@ -1313,7 +1623,7 @@ class Customizer:
|
|
|
1313
1623
|
for x in range(0, self.otcs_settings.replicas_frontend):
|
|
1314
1624
|
pod_name = self.otcs_settings.k8s_statefulset_frontend + "-" + str(x)
|
|
1315
1625
|
|
|
1316
|
-
logger.info("Reactivate Liveness probe for pod -> %s", pod_name)
|
|
1626
|
+
logger.info("Reactivate Liveness probe for pod -> '%s'", pod_name)
|
|
1317
1627
|
self.k8s_object.exec_pod_command(
|
|
1318
1628
|
pod_name, ["/bin/sh", "-c", "rm /tmp/keepalive"]
|
|
1319
1629
|
)
|
|
@@ -1321,7 +1631,7 @@ class Customizer:
|
|
|
1321
1631
|
for x in range(0, self.otcs_settings.replicas_backend):
|
|
1322
1632
|
pod_name = self.otcs_settings.k8s_statefulset_backend + "-" + str(x)
|
|
1323
1633
|
|
|
1324
|
-
logger.info("Reactivate Liveness probe for pod -> %s", pod_name)
|
|
1634
|
+
logger.info("Reactivate Liveness probe for pod -> '%s'", pod_name)
|
|
1325
1635
|
self.k8s_object.exec_pod_command(
|
|
1326
1636
|
pod_name, ["/bin/sh", "-c", "rm /tmp/keepalive"]
|
|
1327
1637
|
)
|
|
@@ -1337,7 +1647,7 @@ class Customizer:
|
|
|
1337
1647
|
time.sleep(extra_wait_time)
|
|
1338
1648
|
logger.info("Continue customizing...")
|
|
1339
1649
|
|
|
1340
|
-
|
|
1650
|
+
# end method definition
|
|
1341
1651
|
|
|
1342
1652
|
def restart_otac_service(self) -> bool:
|
|
1343
1653
|
"""Restart the Archive Center spawner service in OTAC pod
|
|
@@ -1352,7 +1662,7 @@ class Customizer:
|
|
|
1352
1662
|
return False
|
|
1353
1663
|
|
|
1354
1664
|
logger.info(
|
|
1355
|
-
"Restarting spawner service in Archive Center pod -> %s",
|
|
1665
|
+
"Restarting spawner service in Archive Center pod -> '%s'",
|
|
1356
1666
|
self.otac_settings.k8s_pod_name,
|
|
1357
1667
|
)
|
|
1358
1668
|
# The Archive Center Spawner needs to be run in "interactive" mode - otherwise the command will "hang":
|
|
@@ -1370,7 +1680,7 @@ class Customizer:
|
|
|
1370
1680
|
else:
|
|
1371
1681
|
return False
|
|
1372
1682
|
|
|
1373
|
-
|
|
1683
|
+
# end method definition
|
|
1374
1684
|
|
|
1375
1685
|
def restart_otawp_pod(self):
|
|
1376
1686
|
"""Delete the AppWorks Platform Pod to make Kubernetes restart it.
|
|
@@ -1382,7 +1692,7 @@ class Customizer:
|
|
|
1382
1692
|
|
|
1383
1693
|
self.k8s_object.delete_pod(self.otawp_settings.k8s_statefulset + "-0")
|
|
1384
1694
|
|
|
1385
|
-
|
|
1695
|
+
# end method definition
|
|
1386
1696
|
|
|
1387
1697
|
def consolidate_otds(self):
|
|
1388
1698
|
"""Consolidate OTDS resources
|
|
@@ -1395,7 +1705,7 @@ class Customizer:
|
|
|
1395
1705
|
if self.otawp_settings.enabled: # is AppWorks Platform deployed?
|
|
1396
1706
|
self.otds_object.consolidate(self.otawp_settings.resource_name)
|
|
1397
1707
|
|
|
1398
|
-
|
|
1708
|
+
# end method definition
|
|
1399
1709
|
|
|
1400
1710
|
def import_powerdocs_configuration(self, otpd_object: OTPD):
|
|
1401
1711
|
"""Import a database export (zip file) into the PowerDocs database
|
|
@@ -1408,7 +1718,7 @@ class Customizer:
|
|
|
1408
1718
|
# Download file from remote location specified by the OTPD_DBIMPORTFILE
|
|
1409
1719
|
# this must be a public place without authentication:
|
|
1410
1720
|
logger.info(
|
|
1411
|
-
"Download PowerDocs database file from URL -> %s",
|
|
1721
|
+
"Download PowerDocs database file from URL -> '%s'",
|
|
1412
1722
|
self.otpd_settings.db_importfile,
|
|
1413
1723
|
)
|
|
1414
1724
|
|
|
@@ -1416,7 +1726,7 @@ class Customizer:
|
|
|
1416
1726
|
package = requests.get(self.otpd_settings.db_importfile, timeout=60)
|
|
1417
1727
|
package.raise_for_status()
|
|
1418
1728
|
logger.info(
|
|
1419
|
-
"Successfully downloaded PowerDocs database file -> %s; status code -> %s",
|
|
1729
|
+
"Successfully downloaded PowerDocs database file -> '%s'; status code -> %s",
|
|
1420
1730
|
self.otpd_settings.db_importfile,
|
|
1421
1731
|
package.status_code,
|
|
1422
1732
|
)
|
|
@@ -1437,7 +1747,7 @@ class Customizer:
|
|
|
1437
1747
|
except requests.exceptions.HTTPError as err:
|
|
1438
1748
|
logger.error("Request error -> %s", err)
|
|
1439
1749
|
|
|
1440
|
-
|
|
1750
|
+
# end method definition
|
|
1441
1751
|
|
|
1442
1752
|
def set_maintenance_mode(self, enable: bool = True):
|
|
1443
1753
|
"""Enable or Disable Maintenance Mode
|
|
@@ -1475,12 +1785,14 @@ class Customizer:
|
|
|
1475
1785
|
)
|
|
1476
1786
|
logger.info("OTCS frontend is now back in Production Mode!")
|
|
1477
1787
|
|
|
1788
|
+
# end method definition
|
|
1789
|
+
|
|
1478
1790
|
def customization_run(self):
|
|
1479
1791
|
"""Central function to initiate the customization"""
|
|
1480
1792
|
# Set Timer for duration calculation
|
|
1481
|
-
self.settings.customizer_start_time = (
|
|
1482
|
-
|
|
1483
|
-
)
|
|
1793
|
+
self.settings.customizer_start_time = self.settings.customizer_end_time = (
|
|
1794
|
+
datetime.now()
|
|
1795
|
+
)
|
|
1484
1796
|
|
|
1485
1797
|
# Initialize the OTDS, OTCS and OTPD objects and wait for the
|
|
1486
1798
|
# pods to be ready. If any of this fails we bail out:
|
|
@@ -1563,119 +1875,40 @@ class Customizer:
|
|
|
1563
1875
|
else:
|
|
1564
1876
|
self.otpd_object = None
|
|
1565
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
|
+
|
|
1566
1888
|
if (
|
|
1567
1889
|
self.m365_settings.enabled
|
|
1568
1890
|
and self.m365_settings.user != ""
|
|
1569
1891
|
and self.m365_settings.password != ""
|
|
1570
1892
|
): # is M365 enabled?
|
|
1571
|
-
self.log_header("Initialize
|
|
1893
|
+
self.log_header("Initialize Microsoft 365")
|
|
1572
1894
|
|
|
1573
1895
|
# Initialize the M365 object and connection to M365 Graph API:
|
|
1574
1896
|
self.m365_object = self.init_m365()
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
# Download MS Teams App from OTCS (this has with 23.2 a nasty side-effect
|
|
1579
|
-
# of unsetting 2 checkboxes on that config page - we reset these checkboxes
|
|
1580
|
-
# with the settings file "O365Settings.xml"):
|
|
1581
|
-
response = self.otcs_frontend_object.download_config_file(
|
|
1582
|
-
"/cs/cs?func=officegroups.DownloadTeamsPackage",
|
|
1583
|
-
"/tmp/ot.xecm.teams.zip",
|
|
1584
|
-
)
|
|
1585
|
-
# this app upload will be done with the user credentials - this is required:
|
|
1586
|
-
self.m365_object.authenticate_user(
|
|
1587
|
-
self.m365_settings.user, self.m365_settings.password
|
|
1588
|
-
)
|
|
1589
|
-
|
|
1590
|
-
# Check if the app is already installed in the apps catalog:
|
|
1591
|
-
response = self.m365_object.get_teams_apps(
|
|
1592
|
-
f"contains(displayName, '{self.m365_settings.teams_app_name}')"
|
|
1593
|
-
)
|
|
1594
|
-
if self.m365_object.exist_result_item(
|
|
1595
|
-
response, "displayName", self.m365_settings.teams_app_name
|
|
1596
|
-
):
|
|
1597
|
-
app_catalog_id = self.m365_object.get_result_value(
|
|
1598
|
-
response=response, key="id", index=0
|
|
1599
|
-
) # 0 = Index = first item
|
|
1600
|
-
app_catalog_version = self.m365_object.get_result_value(
|
|
1601
|
-
response=response,
|
|
1602
|
-
key="version",
|
|
1603
|
-
index=0,
|
|
1604
|
-
sub_dict_name="appDefinitions",
|
|
1605
|
-
)
|
|
1606
|
-
logger.info(
|
|
1607
|
-
"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...",
|
|
1608
|
-
app_catalog_id,
|
|
1609
|
-
app_catalog_version,
|
|
1610
|
-
)
|
|
1611
|
-
app_upload_version = self.m365_object.extract_version_from_app_manifest(
|
|
1612
|
-
app_path="/tmp/ot.xecm.teams.zip"
|
|
1613
|
-
)
|
|
1614
|
-
if app_catalog_version < app_upload_version:
|
|
1615
|
-
logger.info(
|
|
1616
|
-
"Upgrading Extended ECM Teams App in catalog from version -> %s to version -> %s...",
|
|
1617
|
-
app_catalog_version,
|
|
1618
|
-
app_upload_version,
|
|
1619
|
-
)
|
|
1620
|
-
response = self.m365_object.upload_teams_app(
|
|
1621
|
-
app_path="/tmp/ot.xecm.teams.zip",
|
|
1622
|
-
update_existing_app=True,
|
|
1623
|
-
app_catalog_id=app_catalog_id,
|
|
1624
|
-
)
|
|
1625
|
-
else:
|
|
1626
|
-
logger.info(
|
|
1627
|
-
"No upgrade required. The upload version -> %s is not newer than the version -> %s which is in the M365 app catalog.",
|
|
1628
|
-
app_upload_version,
|
|
1629
|
-
app_catalog_version,
|
|
1630
|
-
)
|
|
1631
|
-
else:
|
|
1632
|
-
logger.info(
|
|
1633
|
-
"Extended Teams ECM App is not yet in app catalog. Installing as new app..."
|
|
1634
|
-
)
|
|
1635
|
-
response = self.m365_object.upload_teams_app(
|
|
1636
|
-
app_path="/tmp/ot.xecm.teams.zip"
|
|
1637
|
-
)
|
|
1638
|
-
|
|
1639
|
-
# logger.info("======== Upload Outlook Add-In ============")
|
|
1640
|
-
|
|
1641
|
-
# # Download MS Outlook Add-In from OTCS:
|
|
1642
|
-
# MANIFEST_FILE = "/tmp/BusinessWorkspace.Manifest.xml"
|
|
1643
|
-
# if not self.otcs_frontend_object.download_config_file(
|
|
1644
|
-
# "/cs/cs?func=outlookaddin.DownloadManifest",
|
|
1645
|
-
# MANIFEST_FILE,
|
|
1646
|
-
# "DeployedContentServer",
|
|
1647
|
-
# self.otcs_settings.public_url,
|
|
1648
|
-
# ):
|
|
1649
|
-
# logger.error("Failed to download M365 Outlook Add-In from Extended ECM!")
|
|
1650
|
-
# else:
|
|
1651
|
-
# # THIS IS NOT IMPLEMENTED DUE TO LACK OF M365 GRAPH API SUPPORT!
|
|
1652
|
-
# # Do it manually for now: https://admin.microsoft.com/#/Settings/IntegratedApps
|
|
1653
|
-
# logger.info("Successfully downloaded M365 Outlook Add-In from Extended ECM to %s", MANIFEST_FILE)
|
|
1654
|
-
# self.m365_object.upload_outlook_app(MANIFEST_FILE)
|
|
1655
|
-
else:
|
|
1656
|
-
self.m365_object = None
|
|
1657
|
-
|
|
1658
|
-
# self.log_header("Initialize Browser Automation...")
|
|
1659
|
-
|
|
1660
|
-
# We initialize a Selenium based browser automation for
|
|
1661
|
-
# those die-hard settings that cannot be automated via REST API
|
|
1662
|
-
# nor LLConfig nor Transport:
|
|
1663
|
-
# self.browser_automation_object = self.init_browser_automation()
|
|
1664
|
-
# if not self.browser_automation_object:
|
|
1665
|
-
# logger.error("Failed to initialize Browser Automation - exiting...")
|
|
1666
|
-
# sys.exit()
|
|
1897
|
+
if not self.m365_object:
|
|
1898
|
+
logger.error("Failed to initialize Microsoft 365!")
|
|
1899
|
+
sys.exit()
|
|
1667
1900
|
|
|
1668
1901
|
self.log_header("Processing Payload")
|
|
1669
1902
|
|
|
1670
1903
|
cust_payload_list = []
|
|
1671
1904
|
# Is uncompressed payload provided?
|
|
1672
1905
|
if os.path.exists(self.settings.cust_payload):
|
|
1673
|
-
logger.info("Found payload file -> %s", self.settings.cust_payload)
|
|
1906
|
+
logger.info("Found payload file -> '%s'", self.settings.cust_payload)
|
|
1674
1907
|
cust_payload_list.append(self.settings.cust_payload)
|
|
1675
1908
|
# Is compressed payload provided?
|
|
1676
1909
|
if os.path.exists(self.settings.cust_payload_gz):
|
|
1677
1910
|
logger.info(
|
|
1678
|
-
"Found compressed payload file -> %s", self.settings.cust_payload_gz
|
|
1911
|
+
"Found compressed payload file -> '%s'", self.settings.cust_payload_gz
|
|
1679
1912
|
)
|
|
1680
1913
|
cust_payload_list.append(self.settings.cust_payload_gz)
|
|
1681
1914
|
|
|
@@ -1683,16 +1916,16 @@ class Customizer:
|
|
|
1683
1916
|
if os.path.exists(self.settings.cust_payload_external):
|
|
1684
1917
|
for filename in os.scandir(self.settings.cust_payload_external):
|
|
1685
1918
|
if filename.is_file() and os.path.getsize(filename) > 0:
|
|
1686
|
-
logger.info("Found external payload file -> %s", filename.path)
|
|
1919
|
+
logger.info("Found external payload file -> '%s'", filename.path)
|
|
1687
1920
|
cust_payload_list.append(filename.path)
|
|
1688
1921
|
else:
|
|
1689
1922
|
logger.info(
|
|
1690
|
-
"No external payload file -> %s", self.settings.cust_payload_external
|
|
1923
|
+
"No external payload file -> '%s'", self.settings.cust_payload_external
|
|
1691
1924
|
)
|
|
1692
1925
|
|
|
1693
1926
|
for cust_payload in cust_payload_list:
|
|
1694
1927
|
# Open the payload file. If this fails we bail out:
|
|
1695
|
-
logger.info("Starting processing of payload -> %s", cust_payload)
|
|
1928
|
+
logger.info("Starting processing of payload -> '%s'", cust_payload)
|
|
1696
1929
|
|
|
1697
1930
|
# Set startTime for duration calculation
|
|
1698
1931
|
start_time = datetime.now()
|
|
@@ -1708,11 +1941,13 @@ class Customizer:
|
|
|
1708
1941
|
otcs_restart_callback=self.restart_otcs_service,
|
|
1709
1942
|
otiv_object=self.otiv_object,
|
|
1710
1943
|
m365_object=self.m365_object,
|
|
1944
|
+
core_share_object=self.core_share_object,
|
|
1711
1945
|
browser_automation_object=self.browser_automation_object,
|
|
1712
1946
|
placeholder_values=self.settings.placeholder_values, # this dict includes placeholder replacements for the Ressource IDs of OTAWP and OTCS
|
|
1713
1947
|
log_header_callback=self.log_header,
|
|
1714
1948
|
stop_on_error=self.settings.stop_on_error,
|
|
1715
1949
|
aviator_enabled=self.aviator_settings.enabled,
|
|
1950
|
+
upload_status_files=self.otcs_settings.upload_status_files,
|
|
1716
1951
|
)
|
|
1717
1952
|
# Load the payload file and initialize the payload sections:
|
|
1718
1953
|
if not payload_object.init_payload():
|
|
@@ -1728,50 +1963,53 @@ class Customizer:
|
|
|
1728
1963
|
self.consolidate_otds()
|
|
1729
1964
|
|
|
1730
1965
|
# Upload payload file for later review to Enterprise Workspace
|
|
1731
|
-
self.
|
|
1732
|
-
|
|
1733
|
-
self.
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
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
|
|
1747
1985
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1986
|
+
with open(cust_payload, "w", encoding="utf-8") as file:
|
|
1987
|
+
yaml.dump(payload_object.get_payload(), file)
|
|
1750
1988
|
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
)
|
|
1757
|
-
target_document_id = self.otcs_backend_object.get_result_value(
|
|
1758
|
-
response, "id"
|
|
1759
|
-
)
|
|
1760
|
-
if target_document_id:
|
|
1761
|
-
response = self.otcs_backend_object.add_document_version(
|
|
1762
|
-
int(target_document_id),
|
|
1763
|
-
cust_payload,
|
|
1764
|
-
os.path.basename(cust_payload),
|
|
1765
|
-
"text/plain",
|
|
1766
|
-
"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)
|
|
1767
1994
|
)
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
cust_payload,
|
|
1771
|
-
os.path.basename(cust_payload),
|
|
1772
|
-
"text/plain",
|
|
1773
|
-
int(target_folder_id),
|
|
1995
|
+
target_document_id = self.otcs_backend_object.get_result_value(
|
|
1996
|
+
response, "id"
|
|
1774
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
|
+
)
|
|
1775
2013
|
|
|
1776
2014
|
duration = datetime.now() - start_time
|
|
1777
2015
|
self.log_header(
|
|
@@ -1819,9 +2057,11 @@ class Customizer:
|
|
|
1819
2057
|
self.otds_object.impersonate_resource(self.otawp_settings.resource_name)
|
|
1820
2058
|
|
|
1821
2059
|
# Upload log file for later review to "Deployment" folder in "Administration" folder
|
|
1822
|
-
if
|
|
2060
|
+
if (
|
|
2061
|
+
os.path.exists(self.settings.cust_log_file)
|
|
2062
|
+
and self.otcs_settings.upload_log_file
|
|
2063
|
+
):
|
|
1823
2064
|
self.log_header("Upload log file to Extended ECM")
|
|
1824
|
-
# logger.info("========== Upload log file to Extended ECM =============")
|
|
1825
2065
|
response = self.otcs_backend_object.get_node_from_nickname(
|
|
1826
2066
|
self.settings.cust_target_folder_nickname
|
|
1827
2067
|
)
|
|
@@ -1839,18 +2079,19 @@ class Customizer:
|
|
|
1839
2079
|
)
|
|
1840
2080
|
if target_document_id:
|
|
1841
2081
|
response = self.otcs_backend_object.add_document_version(
|
|
1842
|
-
int(target_document_id),
|
|
1843
|
-
self.settings.cust_log_file,
|
|
1844
|
-
os.path.basename(self.settings.cust_log_file),
|
|
1845
|
-
"text/plain",
|
|
1846
|
-
"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",
|
|
1847
2087
|
)
|
|
1848
2088
|
else:
|
|
1849
2089
|
response = self.otcs_backend_object.upload_file_to_parent(
|
|
1850
|
-
self.settings.cust_log_file,
|
|
1851
|
-
os.path.basename(self.settings.cust_log_file),
|
|
1852
|
-
"text/plain",
|
|
1853
|
-
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",
|
|
1854
2095
|
)
|
|
1855
2096
|
|
|
1856
2097
|
self.settings.customizer_end_time = datetime.now()
|
|
@@ -1860,29 +2101,4 @@ class Customizer:
|
|
|
1860
2101
|
)
|
|
1861
2102
|
)
|
|
1862
2103
|
|
|
1863
|
-
|
|
1864
|
-
if __name__ == "__main__":
|
|
1865
|
-
logging.basicConfig(
|
|
1866
|
-
format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
|
|
1867
|
-
datefmt="%d-%b-%Y %H:%M:%S",
|
|
1868
|
-
level=logging.INFO,
|
|
1869
|
-
handlers=[
|
|
1870
|
-
logging.StreamHandler(sys.stdout),
|
|
1871
|
-
],
|
|
1872
|
-
)
|
|
1873
|
-
|
|
1874
|
-
my_customizer = Customizer(
|
|
1875
|
-
otcs=CustomizerSettingsOTCS(
|
|
1876
|
-
hostname="otcs.eng.terrarium.cloud",
|
|
1877
|
-
hostname_backend="otcs.eng.terrarium.cloud",
|
|
1878
|
-
hostname_frontend="otcs.eng.terrarium.cloud",
|
|
1879
|
-
protocol="https",
|
|
1880
|
-
port_backend=443,
|
|
1881
|
-
),
|
|
1882
|
-
otds=CustomizerSettingsOTDS(hostname="otds.eng.terrarium.cloud"),
|
|
1883
|
-
otpd=CustomizerSettingsOTPD(enabled=False),
|
|
1884
|
-
k8s=CustomizerSettingsK8S(enabled=False),
|
|
1885
|
-
otiv=CustomizerSettingsOTIV(enabled=True),
|
|
1886
|
-
)
|
|
1887
|
-
|
|
1888
|
-
my_customizer.customization_run()
|
|
2104
|
+
# end method definition
|