pyxecm 1.4__py3-none-any.whl → 1.6__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.

@@ -52,6 +52,10 @@ import sys
52
52
  import time
53
53
  from dataclasses import dataclass, field
54
54
  from datetime import datetime
55
+ import uuid
56
+ import xml.etree.ElementTree as ET
57
+ import json
58
+ import re
55
59
 
56
60
  # from packaging.version import Version
57
61
 
@@ -59,7 +63,8 @@ import requests
59
63
 
60
64
  # OpenText specific modules:
61
65
  import yaml
62
- from pyxecm import OTAC, OTCS, OTDS, OTIV, OTPD
66
+ from pyxecm import OTAC, OTCS, OTDS, OTIV, OTPD, OTMM, CoreShare, OTAWP
67
+ from pyxecm.avts import AVTS
63
68
  from pyxecm.customizer.k8s import K8s
64
69
  from pyxecm.customizer.m365 import M365
65
70
  from pyxecm.customizer.payload import Payload
@@ -74,7 +79,7 @@ class CustomizerSettings:
74
79
  """Class to manage settings"""
75
80
 
76
81
  placeholder_values: dict = field(default_factory=dict)
77
- stop_on_error: bool = os.environ.get("LOGLEVEL", "INFO") == "DEBUG"
82
+ stop_on_error: bool = os.environ.get("STOP_ON_ERROR", "false").lower() == "true"
78
83
  cust_log_file: str = "/tmp/customizing.log"
79
84
  customizer_start_time = customizer_end_time = datetime.now()
80
85
 
@@ -105,6 +110,7 @@ class CustomizerSettingsOTDS:
105
110
  admin_partition: str = "otds.admin"
106
111
  public_url: str = os.environ.get("OTDS_PUBLIC_URL")
107
112
  password: str = os.environ.get("OTDS_PASSWORD")
113
+ bindPassword: str = os.environ.get("BINB_PASSWORD")
108
114
  disable_password_policy: bool = True
109
115
  enable_audit: bool = True
110
116
 
@@ -123,6 +129,8 @@ class CustomizerSettingsOTCS:
123
129
  port: int = os.environ.get("OTCS_SERVICE_PORT_OTCS", 8080)
124
130
  port_backend: int = os.environ.get("OTCS_SERVICE_PORT_OTCS", 8080)
125
131
  port_frontend: int = 80
132
+ base_path: str = "/cs/cs"
133
+ feme_uri: str = os.environ.get("FEME_URI", "ws://feme:4242")
126
134
  admin: str = os.environ.get("OTCS_ADMIN", "admin")
127
135
  password: str = os.environ.get("OTCS_PASSWORD")
128
136
  partition: str = os.environ.get("OTCS_PARTITION", "Content Server Members")
@@ -142,7 +150,11 @@ class CustomizerSettingsOTCS:
142
150
  replicas_frontend = 0
143
151
  replicas_backend = 0
144
152
 
153
+ # Add configuration options for Customizer behaviour
145
154
  update_admin_user: bool = True
155
+ upload_config_files: bool = True
156
+ upload_status_files: bool = True
157
+ upload_log_file: bool = True
146
158
 
147
159
 
148
160
  @dataclass
@@ -212,11 +224,13 @@ class CustomizerSettingsOTAWP:
212
224
  resource_name: str = "awp"
213
225
  access_role_name: str = "Access to " + resource_name
214
226
  admin: str = os.environ.get("OTAWP_ADMIN", "sysadmin")
215
- password: str = os.environ.get("OTAWP_PASSWORD")
227
+ password: str = os.environ.get("OTCS_PASSWORD")
216
228
  public_protocol: str = os.environ.get("OTAWP_PROTOCOL", "https")
217
229
  public_url: str = os.environ.get("OTAWP_PUBLIC_URL")
218
230
  k8s_statefulset: str = "appworks"
219
231
  k8s_configmap: str = "appworks-config-ymls"
232
+ port: int = os.environ.get("OTAWP_SERVICE_PORT", 8080)
233
+ protocol: str = os.environ.get("OTPD_PROTOCOL", "http")
220
234
 
221
235
 
222
236
  @dataclass
@@ -231,7 +245,23 @@ class CustomizerSettingsM365:
231
245
  password: str = os.environ.get("O365_PASSWORD", "")
232
246
  domain: str = os.environ.get("O365_DOMAIN", "")
233
247
  sku_id: str = os.environ.get("O365_SKU_ID", "c7df2760-2c81-4ef7-b578-5b5392b571df")
234
- teams_app_name: str = "OpenText Extended ECM"
248
+ teams_app_name: str = os.environ.get("O365_TEAMS_APP_NAME", "OpenText Extended ECM")
249
+ teams_app_external_id: str = os.environ.get(
250
+ "O365_TEAMS_APP_ID", "dd4af790-d8ff-47a0-87ad-486318272c7a"
251
+ )
252
+
253
+
254
+ @dataclass
255
+ class CustomizerSettingsCoreShare:
256
+ """Class for Core Share related settings"""
257
+
258
+ enabled: bool = os.environ.get("CORE_SHARE_ENABLED", "false").lower() == "true"
259
+ base_url: str = os.environ.get("CORE_SHARE_BASE_URL", "https://core.opentext.com")
260
+ sso_url: str = os.environ.get("CORE_SHARE_SSO_URL", "https://sso.core.opentext.com")
261
+ client_id: str = os.environ.get("CORE_SHARE_CLIENT_ID", "")
262
+ client_secret = os.environ.get("CORE_SHARE_CLIENT_SECRET", "")
263
+ username: str = os.environ.get("CORE_SHARE_USERNAME", "")
264
+ password: str = os.environ.get("CORE_SHARE_PASSWORD", "")
235
265
 
236
266
 
237
267
  @dataclass
@@ -241,6 +271,19 @@ class CustomizerSettingsAviator:
241
271
  enabled: bool = os.environ.get("AVIATOR_ENABLED", "false").lower() == "true"
242
272
 
243
273
 
274
+ @dataclass
275
+ class CustomizerSettingsAVTS:
276
+ """Class for Aviator Search (AVTS) related settings"""
277
+
278
+ enabled: bool = os.environ.get("AVTS_ENABLED", "false").lower() == "true"
279
+ otds_url = os.environ.get("AVTS_OTDS_URL", "")
280
+ client_id = os.environ.get("AVTS_CLIENT_ID", "")
281
+ client_secret = os.environ.get("AVTS_CLIENT_SECRET", "")
282
+ base_url = os.environ.get("AVTS_BASE_URL", "")
283
+ username = os.environ.get("AVTS_USERNAME", "")
284
+ password = os.environ.get("AVTS_PASSWORD", "")
285
+
286
+
244
287
  class Customizer:
245
288
  """Customizer Class to control the cusomization automation
246
289
 
@@ -258,7 +301,9 @@ class Customizer:
258
301
  k8s: CustomizerSettingsK8S = CustomizerSettingsK8S(),
259
302
  otawp: CustomizerSettingsOTAWP = CustomizerSettingsOTAWP(),
260
303
  m365: CustomizerSettingsM365 = CustomizerSettingsM365(),
304
+ core_share: CustomizerSettingsCoreShare = CustomizerSettingsCoreShare(),
261
305
  aviator: CustomizerSettingsAviator = CustomizerSettingsAviator(),
306
+ avts: CustomizerSettingsAVTS = CustomizerSettingsAVTS(),
262
307
  ):
263
308
  self.settings = settings
264
309
 
@@ -286,9 +331,15 @@ class Customizer:
286
331
  # Microsoft 365 Environment variables:
287
332
  self.m365_settings = m365
288
333
 
334
+ # Core Share Environment variables:
335
+ self.core_share_settings = core_share
336
+
289
337
  # Aviator variables:
290
338
  self.aviator_settings = aviator
291
339
 
340
+ # Aviator Search variables:
341
+ self.avts_settings = avts
342
+
292
343
  # Initialize Objects for later assignment
293
344
  self.otds_object: OTDS | None = None
294
345
  self.otcs_object: OTCS | None = None
@@ -299,15 +350,19 @@ class Customizer:
299
350
  self.otiv_object: OTIV | None = None
300
351
  self.k8s_object: K8s | None = None
301
352
  self.m365_object: M365 | None = None
353
+ self.core_share_object: CoreShare | None = None
302
354
  self.browser_automation_object: BrowserAutomation | None = None
355
+ self.otawp_object: OTAWP | None = None
356
+
357
+ # end initializer
303
358
 
304
- def log_header(self, text: str, char: str = "=", length: int = 60):
359
+ def log_header(self, text: str, char: str = "=", length: int = 80):
305
360
  """Helper method to output a section header in the log file
306
361
 
307
362
  Args:
308
- text (str): _description_
363
+ text (str): Headline text to output into the log file.
309
364
  char (str, optional): header line character. Defaults to "=".
310
- length (int, optional): maxium length. Defaults to 60.
365
+ length (int, optional): maxium length. Defaults to 80.
311
366
  Returns:
312
367
  None
313
368
  """
@@ -329,7 +384,7 @@ class Customizer:
329
384
  "%s %s %s", char * char_count, text, char * (char_count + extra_char)
330
385
  )
331
386
 
332
- # end function definition
387
+ # end method definition
333
388
 
334
389
  def init_m365(self) -> M365:
335
390
  """Initialize the M365 object we use to talk to the Microsoft Graph API.
@@ -373,9 +428,13 @@ class Customizer:
373
428
  "Microsoft 365 Default License SKU = %s", self.m365_settings.sku_id
374
429
  )
375
430
  logger.info(
376
- "Microsoft 365 Teams App = %s",
431
+ "Microsoft 365 Teams App Name = %s",
377
432
  self.m365_settings.teams_app_name,
378
433
  )
434
+ logger.info(
435
+ "Microsoft 365 Teams App External ID = %s",
436
+ self.m365_settings.teams_app_external_id,
437
+ )
379
438
 
380
439
  m365_object = M365(
381
440
  tenant_id=self.m365_settings.tenant_id,
@@ -384,16 +443,309 @@ class Customizer:
384
443
  domain=self.m365_settings.domain,
385
444
  sku_id=self.m365_settings.sku_id,
386
445
  teams_app_name=self.m365_settings.teams_app_name,
446
+ teams_app_external_id=self.m365_settings.teams_app_external_id,
387
447
  )
388
448
 
389
449
  if m365_object and m365_object.authenticate():
390
450
  logger.info("Connected to Microsoft Graph API.")
391
- return m365_object
392
451
  else:
393
452
  logger.error("Failed to connect to Microsoft Graph API.")
394
453
  return m365_object
395
454
 
396
- # end function definition
455
+ logger.info(
456
+ "Download M365 Teams App -> '%s' (external ID = %s) from Extended ECM (OTCS)...",
457
+ self.m365_settings.teams_app_name,
458
+ self.m365_settings.teams_app_external_id,
459
+ )
460
+
461
+ # Download MS Teams App from OTCS (this has with 23.2 a nasty side-effect
462
+ # of unsetting 2 checkboxes on that config page - we reset these checkboxes
463
+ # with the settings file "O365Settings.xml"):
464
+ response = self.otcs_frontend_object.download_config_file(
465
+ "/cs/cs?func=officegroups.DownloadTeamsPackage",
466
+ "/tmp/ot.xecm.teams.zip",
467
+ )
468
+ # this app upload will be done with the user credentials - this is required:
469
+ m365_object.authenticate_user(
470
+ self.m365_settings.user, self.m365_settings.password
471
+ )
472
+
473
+ # Check if the app is already installed in the apps catalog
474
+ # ideally we want to use the
475
+ app_exist = False
476
+
477
+ # If the App External ID is provided via Env variable then we
478
+ # prefer to use it instead of the App name:
479
+ if self.m365_settings.teams_app_external_id:
480
+ logger.info(
481
+ "Check if M365 Teams App -> '%s' (%s) is already installed in catalog using external app ID...",
482
+ self.m365_settings.teams_app_name,
483
+ self.m365_settings.teams_app_external_id,
484
+ )
485
+ response = m365_object.get_teams_apps(
486
+ filter_expression="externalId eq '{}'".format(
487
+ self.m365_settings.teams_app_external_id
488
+ )
489
+ )
490
+ # this should always be True as ID is unique:
491
+ app_exist = m365_object.exist_result_item(
492
+ response=response,
493
+ key="externalId",
494
+ value=self.m365_settings.teams_app_external_id,
495
+ )
496
+ # If the app could not be found via the external ID we fall back to
497
+ # search for the app by name:
498
+ if not app_exist:
499
+ if self.m365_settings.teams_app_external_id:
500
+ logger.info(
501
+ "Could not find M365 Teams App using the external ID -> %s. Try to lookup the app by name -> '%s' instead...",
502
+ self.m365_settings.teams_app_external_id,
503
+ self.m365_settings.teams_app_name,
504
+ )
505
+ logger.info(
506
+ "Check if M365 Teams App -> '%s' is already installed in catalog (using app name)...",
507
+ self.m365_settings.teams_app_name,
508
+ )
509
+ response = m365_object.get_teams_apps(
510
+ filter_expression="contains(displayName, '{}')".format(
511
+ self.m365_settings.teams_app_name
512
+ )
513
+ )
514
+ app_exist = m365_object.exist_result_item(
515
+ response=response,
516
+ key="displayName",
517
+ value=self.m365_settings.teams_app_name,
518
+ )
519
+ if app_exist:
520
+ # We double check that we have the effective name of the app
521
+ # in the catalog to avoid errors when the app is looked up
522
+ # by its wrong name in the customizer automation. This can
523
+ # happen if the app is installed manually or the environment
524
+ # variable is set to a wrong name.
525
+ app_catalog_name = m365_object.get_result_value(response, "displayName")
526
+ if app_catalog_name != self.m365_settings.teams_app_name:
527
+ logger.warning(
528
+ "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!",
529
+ app_catalog_name,
530
+ self.m365_settings.teams_app_name,
531
+ )
532
+ # Align the name in the settings dict with the existing name in the catalog.
533
+ self.m365_settings.teams_app_name = app_catalog_name
534
+ # Align the name in the M365 object config dict with the existing name in the catalog.
535
+ m365_object.config()["teamsAppName"] = app_catalog_name
536
+ app_internal_id = m365_object.get_result_value(
537
+ response=response, key="id", index=0
538
+ ) # 0 = Index = first item
539
+ # Store the internal ID for later use
540
+ m365_object.config()["teamsAppInternalId"] = app_internal_id
541
+ app_catalog_version = m365_object.get_result_value(
542
+ response=response,
543
+ key="version",
544
+ index=0,
545
+ sub_dict_name="appDefinitions",
546
+ )
547
+ logger.info(
548
+ "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...",
549
+ self.m365_settings.teams_app_name,
550
+ self.m365_settings.teams_app_external_id,
551
+ app_internal_id,
552
+ app_catalog_version,
553
+ )
554
+ app_download_version = m365_object.extract_version_from_app_manifest(
555
+ app_path="/tmp/ot.xecm.teams.zip"
556
+ )
557
+ if app_catalog_version < app_download_version:
558
+ logger.info(
559
+ "Upgrading Extended ECM Teams App in catalog from version -> %s to version -> %s...",
560
+ app_catalog_version,
561
+ app_download_version,
562
+ )
563
+ response = m365_object.upload_teams_app(
564
+ app_path="/tmp/ot.xecm.teams.zip",
565
+ update_existing_app=True,
566
+ app_catalog_id=app_internal_id,
567
+ )
568
+ app_internal_id = m365_object.get_result_value(
569
+ response=response,
570
+ key="teamsAppId",
571
+ )
572
+ if app_internal_id:
573
+ logger.info(
574
+ "Successfully upgraded Extended ECM Teams App -> %s (external ID = %s). Internal App ID -> %s",
575
+ self.m365_settings.teams_app_name,
576
+ self.m365_settings.teams_app_external_id,
577
+ app_internal_id,
578
+ )
579
+ # Store the internal ID for later use
580
+ m365_object.config()["teamsAppInternalId"] = app_internal_id
581
+ else:
582
+ logger.error(
583
+ "Failed to upgrade Extended ECM Teams App -> %s (external ID = %s).",
584
+ self.m365_settings.teams_app_name,
585
+ self.m365_settings.teams_app_external_id,
586
+ )
587
+ else:
588
+ logger.info(
589
+ "No upgrade required. The downloaded version -> %s is not newer than the version -> %s which is already in the M365 app catalog.",
590
+ app_download_version,
591
+ app_catalog_version,
592
+ )
593
+ else: # Extended ECM M365 Teams app is not yet installed...
594
+ logger.info(
595
+ "Extended Teams ECM App -> '%s' (external ID = %s) is not yet in app catalog. Installing as new app...",
596
+ self.m365_settings.teams_app_name,
597
+ self.m365_settings.teams_app_external_id,
598
+ )
599
+ response = m365_object.upload_teams_app(
600
+ app_path="/tmp/ot.xecm.teams.zip", update_existing_app=False
601
+ )
602
+ app_internal_id = m365_object.get_result_value(
603
+ response=response,
604
+ key="id", # for new installs it is NOT "teamsAppId" but "id" as we use a different M365 Graph API endpoint !!!
605
+ )
606
+ if app_internal_id:
607
+ logger.info(
608
+ "Successfully installed Extended ECM Teams App -> '%s' (external ID = %s). Internal App ID -> %s",
609
+ self.m365_settings.teams_app_name,
610
+ self.m365_settings.teams_app_external_id,
611
+ app_internal_id,
612
+ )
613
+ # Store the internal ID for later use
614
+ m365_object.config()["teamsAppInternalId"] = app_internal_id
615
+ else:
616
+ logger.error(
617
+ "Failed to install Extended ECM Teams App -> '%s' (external ID = %s).",
618
+ self.m365_settings.teams_app_name,
619
+ self.m365_settings.teams_app_external_id,
620
+ )
621
+
622
+ # logger.info("======== Upload Outlook Add-In ============")
623
+
624
+ # # Download MS Outlook Add-In from OTCS:
625
+ # MANIFEST_FILE = "/tmp/BusinessWorkspace.Manifest.xml"
626
+ # if not self.otcs_frontend_object.download_config_file(
627
+ # "/cs/cs?func=outlookaddin.DownloadManifest",
628
+ # MANIFEST_FILE,
629
+ # "DeployedContentServer",
630
+ # self.otcs_settings.public_url,
631
+ # ):
632
+ # logger.error("Failed to download M365 Outlook Add-In from Extended ECM!")
633
+ # else:
634
+ # # THIS IS NOT IMPLEMENTED DUE TO LACK OF M365 GRAPH API SUPPORT!
635
+ # # Do it manually for now: https://admin.microsoft.com/#/Settings/IntegratedApps
636
+ # logger.info("Successfully downloaded M365 Outlook Add-In from Extended ECM to %s", MANIFEST_FILE)
637
+ # m365_object.upload_outlook_app(MANIFEST_FILE)
638
+
639
+ return m365_object
640
+
641
+ # end method definition
642
+
643
+ def init_avts(self) -> AVTS:
644
+ """Initialize the Core Share object we use to talk to the Core Share API.
645
+
646
+ Args:
647
+ None
648
+ Returns:
649
+ object: CoreShare object or None if the object couldn't be created or
650
+ the authentication fails.
651
+ """
652
+
653
+ logger.info(
654
+ "Aviator Search Base URL = %s", self.avts_settings.base_url
655
+ )
656
+ logger.info(
657
+ "Aviator Search OTDS URL = %s", self.avts_settings.otds_url
658
+ )
659
+ logger.info(
660
+ "Aviator Search Client ID = %s", self.avts_settings.client_id
661
+ )
662
+ logger.debug(
663
+ "Aviator Search Client Secret = %s",
664
+ self.avts_settings.client_secret,
665
+ )
666
+ logger.info(
667
+ "Aviator Search User ID = %s", self.avts_settings.username
668
+ )
669
+ logger.debug(
670
+ "Aviator Search User Password = %s",
671
+ self.avts_settings.password,
672
+ )
673
+
674
+ avts_object = AVTS(
675
+ otds_url=self.avts_settings.otds_url,
676
+ base_url=self.avts_settings.base_url,
677
+ client_id=self.avts_settings.client_id,
678
+ client_secret=self.avts_settings.client_secret,
679
+ username=self.avts_settings.username,
680
+ password=self.avts_settings.password,
681
+ )
682
+
683
+ return avts_object
684
+
685
+ # end method definition
686
+
687
+ def init_coreshare(self) -> CoreShare:
688
+ """Initialize the Core Share object we use to talk to the Core Share API.
689
+
690
+ Args:
691
+ None
692
+ Returns:
693
+ object: CoreShare object or None if the object couldn't be created or
694
+ the authentication fails.
695
+ """
696
+
697
+ logger.info(
698
+ "Core Share Base URL = %s", self.core_share_settings.base_url
699
+ )
700
+ logger.info(
701
+ "Core Share SSO URL = %s", self.core_share_settings.sso_url
702
+ )
703
+ logger.info(
704
+ "Core Share Client ID = %s", self.core_share_settings.client_id
705
+ )
706
+ logger.debug(
707
+ "Core Share Client Secret = %s",
708
+ self.core_share_settings.client_secret,
709
+ )
710
+ logger.info(
711
+ "Core Share User = %s",
712
+ (
713
+ self.core_share_settings.username
714
+ if self.core_share_settings.username != ""
715
+ else "<not configured>"
716
+ ),
717
+ )
718
+ logger.debug(
719
+ "Core Share Password = %s",
720
+ (
721
+ self.core_share_settings.password
722
+ if self.core_share_settings.password != ""
723
+ else "<not configured>"
724
+ ),
725
+ )
726
+
727
+ core_share_object = CoreShare(
728
+ base_url=self.core_share_settings.base_url,
729
+ sso_url=self.core_share_settings.sso_url,
730
+ client_id=self.core_share_settings.client_id,
731
+ client_secret=self.core_share_settings.client_secret,
732
+ username=self.core_share_settings.username,
733
+ password=self.core_share_settings.password,
734
+ )
735
+
736
+ if core_share_object and core_share_object.authenticate_admin():
737
+ logger.info("Connected to Core Share as Tenant Admin.")
738
+ else:
739
+ logger.error("Failed to connect to Core Share as Tenant Admin.")
740
+
741
+ if core_share_object and core_share_object.authenticate_user():
742
+ logger.info("Connected to Core Share as Tenant Service User.")
743
+ else:
744
+ logger.error("Failed to connect to Core Share as Tenant Service User.")
745
+
746
+ return core_share_object
747
+
748
+ # end method definition
397
749
 
398
750
  def init_k8s(self) -> K8s:
399
751
  """Initialize the Kubernetes object we use to talk to the Kubernetes API.
@@ -407,10 +759,10 @@ class Customizer:
407
759
  """
408
760
 
409
761
  logger.info("Connection parameters Kubernetes (K8s):")
410
- logger.info("K8s inCluster -> %s", self.k8s_settings.in_cluster)
411
- logger.info("K8s namespace -> %s", self.k8s_settings.namespace)
762
+ logger.info("K8s inCluster = %s", self.k8s_settings.in_cluster)
763
+ logger.info("K8s namespace = %s", self.k8s_settings.namespace)
412
764
  logger.info(
413
- "K8s kubeconfig file -> %s",
765
+ "K8s kubeconfig file = %s",
414
766
  self.k8s_settings.kubeconfig_file,
415
767
  )
416
768
 
@@ -430,14 +782,14 @@ class Customizer:
430
782
  )
431
783
  if not otcs_frontend_scale:
432
784
  logger.error(
433
- "Cannot find Kubernetes Stateful Set for OTCS Frontends -> %s",
785
+ "Cannot find Kubernetes Stateful Set -> '%s' for OTCS Frontends!",
434
786
  self.otcs_settings.k8s_statefulset_frontend,
435
787
  )
436
788
  sys.exit()
437
789
 
438
790
  self.otcs_settings.replicas_frontend = otcs_frontend_scale.spec.replicas # type: ignore
439
791
  logger.info(
440
- "Stateful Set -> %s has -> %s replicas",
792
+ "Stateful Set -> '%s' has -> %s replicas",
441
793
  self.otcs_settings.k8s_statefulset_frontend,
442
794
  self.otcs_settings.replicas_frontend,
443
795
  )
@@ -448,21 +800,21 @@ class Customizer:
448
800
  )
449
801
  if not otcs_backend_scale:
450
802
  logger.error(
451
- "Cannot find Kubernetes Stateful Set for OTCS Backends -> %s",
803
+ "Cannot find Kubernetes Stateful Set -> '%s' for OTCS Backends!",
452
804
  self.otcs_settings.k8s_statefulset_backend,
453
805
  )
454
806
  sys.exit()
455
807
 
456
808
  self.otcs_settings.replicas_backend = otcs_backend_scale.spec.replicas # type: ignore
457
809
  logger.info(
458
- "Stateful Set -> %s has -> %s replicas",
810
+ "Stateful Set -> '%s' has -> %s replicas",
459
811
  self.otcs_settings.k8s_statefulset_backend,
460
812
  self.otcs_settings.replicas_backend,
461
813
  )
462
814
 
463
815
  return k8s_object
464
816
 
465
- # end function definition
817
+ # end method definition
466
818
 
467
819
  def init_otds(self) -> OTDS:
468
820
  """Initialize the OTDS object and parameters and authenticate at OTDS once it is ready.
@@ -491,6 +843,7 @@ class Customizer:
491
843
  username=self.otds_settings.username,
492
844
  password=self.otds_settings.password,
493
845
  otds_ticket=self.otds_settings.otds_ticket,
846
+ bindPassword=self.otds_settings.bindPassword
494
847
  )
495
848
 
496
849
  logger.info("Authenticating to OTDS...")
@@ -519,7 +872,7 @@ class Customizer:
519
872
 
520
873
  return otds_object
521
874
 
522
- # end function definition
875
+ # end method definition
523
876
 
524
877
  def init_otac(self) -> OTAC:
525
878
  """Initialize the OTAC object and parameters.
@@ -557,6 +910,16 @@ class Customizer:
557
910
  self.otds_settings.password,
558
911
  )
559
912
 
913
+ # This is a work-around as OTCS container automation is not
914
+ # enabling the certificate reliable.
915
+ response = otac_object.enable_certificate(
916
+ cert_name="SP_otcs-admin-0", cert_type="ARC"
917
+ )
918
+ if not response:
919
+ logger.error("Failed to enable OTAC certificate for Extended ECM!")
920
+ else:
921
+ logger.info("Successfully enabled OTAC certificate for Extended ECM!")
922
+
560
923
  # is there a known server configured for Archive Center (to sync content with)
561
924
  if otac_object and self.otac_settings.known_server != "":
562
925
  # wait until the OTAC pod is in ready state
@@ -587,7 +950,7 @@ class Customizer:
587
950
 
588
951
  return otac_object
589
952
 
590
- # end function definition
953
+ # end method definition
591
954
 
592
955
  def init_otcs(
593
956
  self,
@@ -630,6 +993,10 @@ class Customizer:
630
993
  "OTCS K8s Backend Pods = %s",
631
994
  self.otcs_settings.k8s_statefulset_backend,
632
995
  )
996
+ logger.info(
997
+ "FEME URI = %s",
998
+ self.otcs_settings.feme_uri,
999
+ )
633
1000
 
634
1001
  logger.debug("Checking if OTCS object has already been initialized")
635
1002
 
@@ -646,6 +1013,8 @@ class Customizer:
646
1013
  partition_name,
647
1014
  resource_name,
648
1015
  otds_ticket=otds_ticket,
1016
+ base_path=self.otcs_settings.base_path,
1017
+ feme_uri=self.otcs_settings.feme_uri,
649
1018
  )
650
1019
 
651
1020
  # It is important to wait for OTCS to be configured - otherwise we
@@ -666,17 +1035,17 @@ class Customizer:
666
1035
  otcs_cookie = otcs_object.authenticate()
667
1036
  logger.info("OTCS is ready now.")
668
1037
 
669
- if self.otcs_settings.update_admin_user:
670
- # Set first name and last name of Admin user (ID = 1000):
671
- otcs_object.update_user(1000, field="first_name", value="Terrarium")
672
- otcs_object.update_user(1000, field="last_name", value="Admin")
1038
+ # if self.otcs_settings.update_admin_user:
1039
+ # Set first name and last name of Admin user (ID = 1000):
1040
+ # otcs_object.update_user(1000, field="first_name", value="Terrarium")
1041
+ # otcs_object.update_user(1000, field="last_name", value="Admin")
673
1042
 
674
1043
  if "OTCS_RESSOURCE_ID" not in self.settings.placeholder_values:
675
- self.settings.placeholder_values[
676
- "OTCS_RESSOURCE_ID"
677
- ] = self.otds_object.get_resource(self.otcs_settings.resource_name)[
678
- "resourceID"
679
- ]
1044
+ self.settings.placeholder_values["OTCS_RESSOURCE_ID"] = (
1045
+ self.otds_object.get_resource(self.otcs_settings.resource_name)[
1046
+ "resourceID"
1047
+ ]
1048
+ )
680
1049
  logger.debug(
681
1050
  "Placeholder values after OTCS init = %s",
682
1051
  self.settings.placeholder_values,
@@ -686,9 +1055,9 @@ class Customizer:
686
1055
  otcs_resource = self.otds_object.get_resource(
687
1056
  self.otcs_settings.resource_name
688
1057
  )
689
- otcs_resource[
690
- "logoutURL"
691
- ] = f"{self.otawp_settings.public_protocol}://{self.otawp_settings.public_url}/home/system/wcp/sso/sso_logout.htm"
1058
+ otcs_resource["logoutURL"] = (
1059
+ f"{self.otawp_settings.public_protocol}://{self.otawp_settings.public_url}/home/system/wcp/sso/sso_logout.htm"
1060
+ )
692
1061
  otcs_resource["logoutMethod"] = "GET"
693
1062
 
694
1063
  self.otds_object.update_resource(name="cs", resource=otcs_resource)
@@ -698,7 +1067,7 @@ class Customizer:
698
1067
 
699
1068
  return otcs_object
700
1069
 
701
- # end function definition
1070
+ # end method definition
702
1071
 
703
1072
  def init_otiv(self) -> OTIV | None:
704
1073
  """Initialize the OTIV (Intelligent Viewing) object and its OTDS settings.
@@ -750,9 +1119,27 @@ class Customizer:
750
1119
  )
751
1120
  return None
752
1121
 
1122
+ # Workaround for VAT-4580 (24.2.0)
1123
+ update_publisher = self.otds_object.update_user(
1124
+ partition="Content Server Service Users",
1125
+ user_id="iv-publisher",
1126
+ attribute_name="oTType",
1127
+ attribute_value="ServiceUser",
1128
+ )
1129
+ while update_publisher is None:
1130
+ update_publisher = self.otds_object.update_user(
1131
+ partition="Content Server Service Users",
1132
+ user_id="iv-publisher",
1133
+ attribute_name="oTType",
1134
+ attribute_value="ServiceUser",
1135
+ )
1136
+ time.sleep(30)
1137
+
1138
+ logger.info("OTDS user iv-publisher -> updating oTType=ServiceUser")
1139
+
753
1140
  return otiv_object
754
1141
 
755
- # end function definition
1142
+ # end method definition
756
1143
 
757
1144
  def init_otpd(self) -> OTPD:
758
1145
  """Initialize the OTPD (PowerDocs) object and parameters.
@@ -829,7 +1216,7 @@ class Customizer:
829
1216
  logger.info("OTAWP K8s Config Map = %s", self.otawp_settings.k8s_configmap)
830
1217
 
831
1218
  logger.info(
832
- "Wait for OTCS to create its OTDS resource with name -> %s...",
1219
+ "Wait for OTCS to create its OTDS resource with name -> '%s'...",
833
1220
  self.otcs_settings.resource_name,
834
1221
  )
835
1222
 
@@ -838,7 +1225,7 @@ class Customizer:
838
1225
  otcs_resource = self.otds_object.get_resource(self.otcs_settings.resource_name)
839
1226
  while otcs_resource is None:
840
1227
  logger.warning(
841
- "OTDS resource for Content Server with name -> %s does not exist yet. Waiting...",
1228
+ "OTDS resource for Content Server with name -> '%s' does not exist yet. Waiting...",
842
1229
  self.otcs_settings.resource_name,
843
1230
  )
844
1231
  time.sleep(30)
@@ -854,7 +1241,7 @@ class Customizer:
854
1241
  awp_resource = self.otds_object.get_resource(self.otawp_settings.resource_name)
855
1242
  if not awp_resource:
856
1243
  logger.info(
857
- "OTDS resource -> %s for AppWorks Platform does not yet exist. Creating...",
1244
+ "OTDS resource -> '%s' for AppWorks Platform does not yet exist. Creating...",
858
1245
  self.otawp_settings.resource_name,
859
1246
  )
860
1247
  # Create a Python dict with the special payload we need for AppWorks:
@@ -1067,10 +1454,10 @@ class Customizer:
1067
1454
  ]
1068
1455
 
1069
1456
  awp_resource = self.otds_object.add_resource(
1070
- self.otawp_settings.resource_name,
1071
- "AppWorks Platform",
1072
- "AppWorks Platform",
1073
- additional_payload,
1457
+ name=self.otawp_settings.resource_name,
1458
+ description="AppWorks Platform",
1459
+ display_name="AppWorks Platform",
1460
+ additional_payload=additional_payload,
1074
1461
  )
1075
1462
  else:
1076
1463
  logger.info(
@@ -1150,7 +1537,7 @@ class Customizer:
1150
1537
  )
1151
1538
  while otcs_partition is None:
1152
1539
  logger.warning(
1153
- "OTDS user partition for Content Server with name -> %s does not exist yet. Waiting...",
1540
+ "OTDS user partition for Content Server with name -> '%s' does not exist yet. Waiting...",
1154
1541
  self.otcs_settings.partition,
1155
1542
  )
1156
1543
 
@@ -1188,7 +1575,7 @@ class Customizer:
1188
1575
  # check if the license file exists, otherwise skip for versions pre 24.1
1189
1576
  if os.path.isfile(self.otawp_settings.license_file):
1190
1577
  logger.info(
1191
- "OTAWP license file (%s) found, assiging to ressource %s",
1578
+ "Found OTAWP license file -> '%s', assiging it to ressource '%s'...",
1192
1579
  self.otawp_settings.license_file,
1193
1580
  self.otawp_settings.resource_name,
1194
1581
  )
@@ -1201,14 +1588,14 @@ class Customizer:
1201
1588
  )
1202
1589
  if not otawp_license:
1203
1590
  logger.error(
1204
- "Couldn't apply license -> %s for product -> %s to OTDS resource -> %s",
1591
+ "Couldn't apply license -> '%s' for product -> '%s' to OTDS resource -> '%s'",
1205
1592
  self.otawp_settings.license_file,
1206
1593
  self.otawp_settings.product_name,
1207
1594
  awp_resource["resourceID"],
1208
1595
  )
1209
1596
  else:
1210
1597
  logger.info(
1211
- "Successfully applied license -> %s for product -> %s to OTDS resource -> %s",
1598
+ "Successfully applied license -> '%s' for product -> '%s' to OTDS resource -> '%s'",
1212
1599
  self.otawp_settings.license_file,
1213
1600
  self.otawp_settings.product_name,
1214
1601
  awp_resource["resourceID"],
@@ -1237,20 +1624,29 @@ class Customizer:
1237
1624
  )
1238
1625
  if not assigned_license:
1239
1626
  logger.error(
1240
- "Partition -> %s could not be assigned to license -> %s (%s)",
1627
+ "Partition -> '%s' could not be assigned to license -> '%s' (%s)",
1241
1628
  partition_name,
1242
1629
  self.otawp_settings.product_name,
1243
1630
  "USERS",
1244
1631
  )
1245
1632
  else:
1246
1633
  logger.info(
1247
- "Partition -> %s successfully assigned to license -> %s (%s)",
1634
+ "Partition -> '%s' successfully assigned to license -> '%s' (%s)",
1248
1635
  partition_name,
1249
1636
  self.otawp_settings.product_name,
1250
1637
  "USERS",
1251
1638
  )
1639
+ otawp_object = OTAWP(
1640
+ self.otawp_settings.protocol,
1641
+ self.otawp_settings.k8s_statefulset,
1642
+ str(self.otawp_settings.port),
1643
+ "sysadmin",
1644
+ self.otawp_settings.password,
1645
+ "",
1646
+ )
1647
+ return otawp_object
1252
1648
 
1253
- # end function definition
1649
+ # end method definition
1254
1650
 
1255
1651
  def restart_otcs_service(self, otcs_object: OTCS, extra_wait_time: int = 60):
1256
1652
  """Restart the Content Server service in all OTCS pods
@@ -1273,32 +1669,44 @@ class Customizer:
1273
1669
  for x in range(0, self.otcs_settings.replicas_frontend):
1274
1670
  pod_name = self.otcs_settings.k8s_statefulset_frontend + "-" + str(x)
1275
1671
 
1276
- logger.info("Deactivate Liveness probe for pod -> %s", pod_name)
1672
+ logger.info("Deactivate Liveness probe for pod -> '%s'", pod_name)
1277
1673
  self.k8s_object.exec_pod_command(
1278
- pod_name, ["/bin/sh", "-c", "touch /tmp/keepalive"]
1674
+ pod_name,
1675
+ ["/bin/sh", "-c", "touch /tmp/keepalive"],
1676
+ container="otcs-frontend-container",
1279
1677
  )
1280
- logger.info("Restarting pod -> %s", pod_name)
1678
+ logger.info("Restarting pod -> '%s'", pod_name)
1281
1679
  self.k8s_object.exec_pod_command(
1282
- pod_name, ["/bin/sh", "-c", "/opt/opentext/cs/stop_csserver"]
1680
+ pod_name,
1681
+ ["/bin/sh", "-c", "/opt/opentext/cs/stop_csserver"],
1682
+ container="otcs-frontend-container",
1283
1683
  )
1284
1684
  self.k8s_object.exec_pod_command(
1285
- pod_name, ["/bin/sh", "-c", "/opt/opentext/cs/start_csserver"]
1685
+ pod_name,
1686
+ ["/bin/sh", "-c", "/opt/opentext/cs/start_csserver"],
1687
+ container="otcs-frontend-container",
1286
1688
  )
1287
1689
 
1288
1690
  # Restart all backends:
1289
1691
  for x in range(0, self.otcs_settings.replicas_backend):
1290
1692
  pod_name = self.otcs_settings.k8s_statefulset_backend + "-" + str(x)
1291
1693
 
1292
- logger.info("Deactivate Liveness probe for pod -> %s", pod_name)
1694
+ logger.info("Deactivate Liveness probe for pod -> '%s'", pod_name)
1293
1695
  self.k8s_object.exec_pod_command(
1294
- pod_name, ["/bin/sh", "-c", "touch /tmp/keepalive"]
1696
+ pod_name,
1697
+ ["/bin/sh", "-c", "touch /tmp/keepalive"],
1698
+ container="otcs-admin-container",
1295
1699
  )
1296
- logger.info("Restarting pod -> %s", pod_name)
1700
+ logger.info("Restarting pod -> '%s'", pod_name)
1297
1701
  self.k8s_object.exec_pod_command(
1298
- pod_name, ["/bin/sh", "-c", "/opt/opentext/cs/stop_csserver"]
1702
+ pod_name,
1703
+ ["/bin/sh", "-c", "/opt/opentext/cs/stop_csserver"],
1704
+ container="otcs-admin-container",
1299
1705
  )
1300
1706
  self.k8s_object.exec_pod_command(
1301
- pod_name, ["/bin/sh", "-c", "/opt/opentext/cs/start_csserver"]
1707
+ pod_name,
1708
+ ["/bin/sh", "-c", "/opt/opentext/cs/start_csserver"],
1709
+ container="otcs-admin-container",
1302
1710
  )
1303
1711
 
1304
1712
  logger.info("Re-Authenticating to OTCS after restart of pods...")
@@ -1313,17 +1721,21 @@ class Customizer:
1313
1721
  for x in range(0, self.otcs_settings.replicas_frontend):
1314
1722
  pod_name = self.otcs_settings.k8s_statefulset_frontend + "-" + str(x)
1315
1723
 
1316
- logger.info("Reactivate Liveness probe for pod -> %s", pod_name)
1724
+ logger.info("Reactivate Liveness probe for pod -> '%s'", pod_name)
1317
1725
  self.k8s_object.exec_pod_command(
1318
- pod_name, ["/bin/sh", "-c", "rm /tmp/keepalive"]
1726
+ pod_name,
1727
+ ["/bin/sh", "-c", "rm /tmp/keepalive"],
1728
+ container="otcs-frontend-container",
1319
1729
  )
1320
1730
 
1321
1731
  for x in range(0, self.otcs_settings.replicas_backend):
1322
1732
  pod_name = self.otcs_settings.k8s_statefulset_backend + "-" + str(x)
1323
1733
 
1324
- logger.info("Reactivate Liveness probe for pod -> %s", pod_name)
1734
+ logger.info("Reactivate Liveness probe for pod -> '%s'", pod_name)
1325
1735
  self.k8s_object.exec_pod_command(
1326
- pod_name, ["/bin/sh", "-c", "rm /tmp/keepalive"]
1736
+ pod_name,
1737
+ ["/bin/sh", "-c", "rm /tmp/keepalive"],
1738
+ container="otcs-admin-container",
1327
1739
  )
1328
1740
 
1329
1741
  logger.info("Restart OTCS frontend and backend pods has been completed.")
@@ -1337,7 +1749,7 @@ class Customizer:
1337
1749
  time.sleep(extra_wait_time)
1338
1750
  logger.info("Continue customizing...")
1339
1751
 
1340
- # end function definition
1752
+ # end method definition
1341
1753
 
1342
1754
  def restart_otac_service(self) -> bool:
1343
1755
  """Restart the Archive Center spawner service in OTAC pod
@@ -1352,7 +1764,7 @@ class Customizer:
1352
1764
  return False
1353
1765
 
1354
1766
  logger.info(
1355
- "Restarting spawner service in Archive Center pod -> %s",
1767
+ "Restarting spawner service in Archive Center pod -> '%s'",
1356
1768
  self.otac_settings.k8s_pod_name,
1357
1769
  )
1358
1770
  # The Archive Center Spawner needs to be run in "interactive" mode - otherwise the command will "hang":
@@ -1370,7 +1782,7 @@ class Customizer:
1370
1782
  else:
1371
1783
  return False
1372
1784
 
1373
- # end function definition
1785
+ # end method definition
1374
1786
 
1375
1787
  def restart_otawp_pod(self):
1376
1788
  """Delete the AppWorks Platform Pod to make Kubernetes restart it.
@@ -1382,7 +1794,7 @@ class Customizer:
1382
1794
 
1383
1795
  self.k8s_object.delete_pod(self.otawp_settings.k8s_statefulset + "-0")
1384
1796
 
1385
- # end function definition
1797
+ # end method definition
1386
1798
 
1387
1799
  def consolidate_otds(self):
1388
1800
  """Consolidate OTDS resources
@@ -1395,7 +1807,7 @@ class Customizer:
1395
1807
  if self.otawp_settings.enabled: # is AppWorks Platform deployed?
1396
1808
  self.otds_object.consolidate(self.otawp_settings.resource_name)
1397
1809
 
1398
- # end function definition
1810
+ # end method definition
1399
1811
 
1400
1812
  def import_powerdocs_configuration(self, otpd_object: OTPD):
1401
1813
  """Import a database export (zip file) into the PowerDocs database
@@ -1408,7 +1820,7 @@ class Customizer:
1408
1820
  # Download file from remote location specified by the OTPD_DBIMPORTFILE
1409
1821
  # this must be a public place without authentication:
1410
1822
  logger.info(
1411
- "Download PowerDocs database file from URL -> %s",
1823
+ "Download PowerDocs database file from URL -> '%s'",
1412
1824
  self.otpd_settings.db_importfile,
1413
1825
  )
1414
1826
 
@@ -1416,7 +1828,7 @@ class Customizer:
1416
1828
  package = requests.get(self.otpd_settings.db_importfile, timeout=60)
1417
1829
  package.raise_for_status()
1418
1830
  logger.info(
1419
- "Successfully downloaded PowerDocs database file -> %s; status code -> %s",
1831
+ "Successfully downloaded PowerDocs database file -> '%s'; status code -> %s",
1420
1832
  self.otpd_settings.db_importfile,
1421
1833
  package.status_code,
1422
1834
  )
@@ -1437,7 +1849,7 @@ class Customizer:
1437
1849
  except requests.exceptions.HTTPError as err:
1438
1850
  logger.error("Request error -> %s", err)
1439
1851
 
1440
- # end function definition
1852
+ # end method definition
1441
1853
 
1442
1854
  def set_maintenance_mode(self, enable: bool = True):
1443
1855
  """Enable or Disable Maintenance Mode
@@ -1475,12 +1887,14 @@ class Customizer:
1475
1887
  )
1476
1888
  logger.info("OTCS frontend is now back in Production Mode!")
1477
1889
 
1890
+ # end method definition
1891
+
1478
1892
  def customization_run(self):
1479
1893
  """Central function to initiate the customization"""
1480
1894
  # Set Timer for duration calculation
1481
- self.settings.customizer_start_time = (
1482
- self.settings.customizer_end_time
1483
- ) = datetime.now()
1895
+ self.settings.customizer_start_time = self.settings.customizer_end_time = (
1896
+ datetime.now()
1897
+ )
1484
1898
 
1485
1899
  # Initialize the OTDS, OTCS and OTPD objects and wait for the
1486
1900
  # pods to be ready. If any of this fails we bail out:
@@ -1510,7 +1924,7 @@ class Customizer:
1510
1924
  self.log_header("Initialize OTAWP")
1511
1925
 
1512
1926
  # Configure required OTDS resources as AppWorks doesn't do this on its own:
1513
- self.init_otawp()
1927
+ self.otawp_object = self.init_otawp()
1514
1928
  else:
1515
1929
  self.settings.placeholder_values["OTAWP_RESOURCE_ID"] = ""
1516
1930
 
@@ -1563,136 +1977,68 @@ class Customizer:
1563
1977
  else:
1564
1978
  self.otpd_object = None
1565
1979
 
1980
+ if self.core_share_settings.enabled: # is Core Share enabled?
1981
+ self.log_header("Initialize Core Share")
1982
+
1983
+ self.core_share_object = self.init_coreshare()
1984
+ if not self.core_share_object:
1985
+ logger.error("Failed to initialize Core Share - exiting...")
1986
+ sys.exit()
1987
+ else:
1988
+ self.core_share_object = None
1989
+
1566
1990
  if (
1567
1991
  self.m365_settings.enabled
1568
1992
  and self.m365_settings.user != ""
1569
1993
  and self.m365_settings.password != ""
1570
1994
  ): # is M365 enabled?
1571
- self.log_header("Initialize MS Graph API")
1995
+ self.log_header("Initialize Microsoft 365")
1572
1996
 
1573
1997
  # Initialize the M365 object and connection to M365 Graph API:
1574
1998
  self.m365_object = self.init_m365()
1999
+ if not self.m365_object:
2000
+ logger.error("Failed to initialize Microsoft 365!")
2001
+ sys.exit()
1575
2002
 
1576
- self.log_header("Upload MS Teams App")
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)
2003
+ if self.avts_settings.enabled:
2004
+ self.log_header("Initialize Aviator Search")
2005
+ self.avts_object = self.init_avts()
2006
+ if not self.avts_object:
2007
+ logger.error("Failed to initialize Aviator Search")
2008
+ sys.exit()
1655
2009
  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()
2010
+ self.avts_object = None
1667
2011
 
1668
2012
  self.log_header("Processing Payload")
1669
2013
 
1670
2014
  cust_payload_list = []
1671
2015
  # Is uncompressed payload provided?
1672
2016
  if os.path.exists(self.settings.cust_payload):
1673
- logger.info("Found payload file -> %s", self.settings.cust_payload)
2017
+ logger.info("Found payload file -> '%s'", self.settings.cust_payload)
1674
2018
  cust_payload_list.append(self.settings.cust_payload)
1675
2019
  # Is compressed payload provided?
1676
2020
  if os.path.exists(self.settings.cust_payload_gz):
1677
2021
  logger.info(
1678
- "Found compressed payload file -> %s", self.settings.cust_payload_gz
2022
+ "Found compressed payload file -> '%s'", self.settings.cust_payload_gz
1679
2023
  )
1680
2024
  cust_payload_list.append(self.settings.cust_payload_gz)
1681
2025
 
1682
2026
  # do we have additional payload as an external file?
1683
2027
  if os.path.exists(self.settings.cust_payload_external):
1684
- for filename in os.scandir(self.settings.cust_payload_external):
2028
+ for filename in sorted(
2029
+ os.scandir(self.settings.cust_payload_external), key=lambda e: e.name
2030
+ ):
1685
2031
  if filename.is_file() and os.path.getsize(filename) > 0:
1686
- logger.info("Found external payload file -> %s", filename.path)
2032
+ logger.info("Found external payload file -> '%s'", filename.path)
1687
2033
  cust_payload_list.append(filename.path)
1688
2034
  else:
1689
2035
  logger.info(
1690
- "No external payload file -> %s", self.settings.cust_payload_external
2036
+ "No external payload file -> '%s'", self.settings.cust_payload_external
1691
2037
  )
1692
2038
 
1693
2039
  for cust_payload in cust_payload_list:
1694
2040
  # Open the payload file. If this fails we bail out:
1695
- logger.info("Starting processing of payload -> %s", cust_payload)
2041
+ logger.info("Starting processing of payload -> '%s'", cust_payload)
1696
2042
 
1697
2043
  # Set startTime for duration calculation
1698
2044
  start_time = datetime.now()
@@ -1708,11 +2054,15 @@ class Customizer:
1708
2054
  otcs_restart_callback=self.restart_otcs_service,
1709
2055
  otiv_object=self.otiv_object,
1710
2056
  m365_object=self.m365_object,
2057
+ core_share_object=self.core_share_object,
1711
2058
  browser_automation_object=self.browser_automation_object,
1712
2059
  placeholder_values=self.settings.placeholder_values, # this dict includes placeholder replacements for the Ressource IDs of OTAWP and OTCS
1713
2060
  log_header_callback=self.log_header,
1714
2061
  stop_on_error=self.settings.stop_on_error,
1715
2062
  aviator_enabled=self.aviator_settings.enabled,
2063
+ upload_status_files=self.otcs_settings.upload_status_files,
2064
+ otawp_object=self.otawp_object,
2065
+ avts_object=self.avts_object,
1716
2066
  )
1717
2067
  # Load the payload file and initialize the payload sections:
1718
2068
  if not payload_object.init_payload():
@@ -1728,50 +2078,53 @@ class Customizer:
1728
2078
  self.consolidate_otds()
1729
2079
 
1730
2080
  # Upload payload file for later review to Enterprise Workspace
1731
- self.log_header("Upload Payload file to Extended ECM")
1732
- response = self.otcs_backend_object.get_node_from_nickname(
1733
- self.settings.cust_target_folder_nickname
1734
- )
1735
- target_folder_id = self.otcs_backend_object.get_result_value(response, "id")
1736
- if not target_folder_id:
1737
- target_folder_id = 2000 # use Enterprise Workspace as fallback
1738
- # Write YAML file with upadated payload (including IDs, etc.).
1739
- # We need to write to /tmp as initial location is read-only:
1740
- payload_file = os.path.basename(cust_payload)
1741
- payload_file = (
1742
- payload_file[: -len(".gz.b64")]
1743
- if payload_file.endswith(".gz.b64")
1744
- else payload_file
1745
- )
1746
- cust_payload = "/tmp/" + payload_file
2081
+ if self.otcs_settings.upload_config_files:
2082
+ self.log_header("Upload Payload file to Extended ECM")
2083
+ response = self.otcs_backend_object.get_node_from_nickname(
2084
+ self.settings.cust_target_folder_nickname
2085
+ )
2086
+ target_folder_id = self.otcs_backend_object.get_result_value(
2087
+ response, "id"
2088
+ )
2089
+ if not target_folder_id:
2090
+ target_folder_id = 2000 # use Enterprise Workspace as fallback
2091
+ # Write YAML file with upadated payload (including IDs, etc.).
2092
+ # We need to write to /tmp as initial location is read-only:
2093
+ payload_file = os.path.basename(cust_payload)
2094
+ payload_file = (
2095
+ payload_file[: -len(".gz.b64")]
2096
+ if payload_file.endswith(".gz.b64")
2097
+ else payload_file
2098
+ )
2099
+ cust_payload = "/tmp/" + payload_file
1747
2100
 
1748
- with open(cust_payload, "w", encoding="utf-8") as file:
1749
- yaml.dump(payload_object.get_payload(), file)
2101
+ with open(cust_payload, "w", encoding="utf-8") as file:
2102
+ yaml.dump(payload_object.get_payload(), file)
1750
2103
 
1751
- # Check if the payload file has been uploaded before.
1752
- # This can happen if we re-run the python container.
1753
- # In this case we add a version to the existing document:
1754
- response = self.otcs_backend_object.get_node_by_parent_and_name(
1755
- int(target_folder_id), os.path.basename(cust_payload)
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",
2104
+ # Check if the payload file has been uploaded before.
2105
+ # This can happen if we re-run the python container.
2106
+ # In this case we add a version to the existing document:
2107
+ response = self.otcs_backend_object.get_node_by_parent_and_name(
2108
+ int(target_folder_id), os.path.basename(cust_payload)
1767
2109
  )
1768
- else:
1769
- response = self.otcs_backend_object.upload_file_to_parent(
1770
- cust_payload,
1771
- os.path.basename(cust_payload),
1772
- "text/plain",
1773
- int(target_folder_id),
2110
+ target_document_id = self.otcs_backend_object.get_result_value(
2111
+ response, "id"
1774
2112
  )
2113
+ if target_document_id:
2114
+ response = self.otcs_backend_object.add_document_version(
2115
+ int(target_document_id),
2116
+ cust_payload,
2117
+ os.path.basename(cust_payload),
2118
+ "text/plain",
2119
+ "Updated payload file after re-run of customization",
2120
+ )
2121
+ else:
2122
+ response = self.otcs_backend_object.upload_file_to_parent(
2123
+ cust_payload,
2124
+ os.path.basename(cust_payload),
2125
+ "text/plain",
2126
+ int(target_folder_id),
2127
+ )
1775
2128
 
1776
2129
  duration = datetime.now() - start_time
1777
2130
  self.log_header(
@@ -1819,9 +2172,11 @@ class Customizer:
1819
2172
  self.otds_object.impersonate_resource(self.otawp_settings.resource_name)
1820
2173
 
1821
2174
  # Upload log file for later review to "Deployment" folder in "Administration" folder
1822
- if os.path.exists(self.settings.cust_log_file):
2175
+ if (
2176
+ os.path.exists(self.settings.cust_log_file)
2177
+ and self.otcs_settings.upload_log_file
2178
+ ):
1823
2179
  self.log_header("Upload log file to Extended ECM")
1824
- # logger.info("========== Upload log file to Extended ECM =============")
1825
2180
  response = self.otcs_backend_object.get_node_from_nickname(
1826
2181
  self.settings.cust_target_folder_nickname
1827
2182
  )
@@ -1839,18 +2194,19 @@ class Customizer:
1839
2194
  )
1840
2195
  if target_document_id:
1841
2196
  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",
2197
+ node_id=int(target_document_id),
2198
+ file_url=self.settings.cust_log_file,
2199
+ file_name=os.path.basename(self.settings.cust_log_file),
2200
+ mime_type="text/plain",
2201
+ description="Updated Python Log after re-run of customization",
1847
2202
  )
1848
2203
  else:
1849
2204
  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),
2205
+ file_url=self.settings.cust_log_file,
2206
+ file_name=os.path.basename(self.settings.cust_log_file),
2207
+ mime_type="text/plain",
2208
+ parent_id=int(target_folder_id),
2209
+ description="Initial Python Log after first run of customization",
1854
2210
  )
1855
2211
 
1856
2212
  self.settings.customizer_end_time = datetime.now()
@@ -1873,16 +2229,17 @@ if __name__ == "__main__":
1873
2229
 
1874
2230
  my_customizer = Customizer(
1875
2231
  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,
2232
+ hostname="otcs.local.xecm.cloud",
2233
+ hostname_backend="otcs-admin-0",
2234
+ hostname_frontend="otcs-frontend",
2235
+ protocol="http",
2236
+ port_backend=8080,
1881
2237
  ),
1882
- otds=CustomizerSettingsOTDS(hostname="otds.eng.terrarium.cloud"),
2238
+ otds=CustomizerSettingsOTDS(hostname="otds"),
1883
2239
  otpd=CustomizerSettingsOTPD(enabled=False),
1884
- k8s=CustomizerSettingsK8S(enabled=False),
1885
- otiv=CustomizerSettingsOTIV(enabled=True),
2240
+ otac=CustomizerSettingsOTAC(enabled=False),
2241
+ k8s=CustomizerSettingsK8S(enabled=True),
2242
+ otiv=CustomizerSettingsOTIV(enabled=False),
1886
2243
  )
1887
2244
 
1888
2245
  my_customizer.customization_run()