pyxecm 1.6__py3-none-any.whl → 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyxecm might be problematic. Click here for more details.

Files changed (56) hide show
  1. pyxecm/__init__.py +6 -4
  2. pyxecm/avts.py +673 -246
  3. pyxecm/coreshare.py +686 -467
  4. pyxecm/customizer/__init__.py +16 -4
  5. pyxecm/customizer/__main__.py +58 -0
  6. pyxecm/customizer/api/__init__.py +5 -0
  7. pyxecm/customizer/api/__main__.py +6 -0
  8. pyxecm/customizer/api/app.py +914 -0
  9. pyxecm/customizer/api/auth.py +154 -0
  10. pyxecm/customizer/api/metrics.py +92 -0
  11. pyxecm/customizer/api/models.py +13 -0
  12. pyxecm/customizer/api/payload_list.py +865 -0
  13. pyxecm/customizer/api/settings.py +103 -0
  14. pyxecm/customizer/browser_automation.py +332 -139
  15. pyxecm/customizer/customizer.py +1007 -1130
  16. pyxecm/customizer/exceptions.py +35 -0
  17. pyxecm/customizer/guidewire.py +322 -0
  18. pyxecm/customizer/k8s.py +713 -378
  19. pyxecm/customizer/log.py +107 -0
  20. pyxecm/customizer/m365.py +2867 -909
  21. pyxecm/customizer/nhc.py +1169 -0
  22. pyxecm/customizer/openapi.py +258 -0
  23. pyxecm/customizer/payload.py +16817 -7467
  24. pyxecm/customizer/pht.py +699 -285
  25. pyxecm/customizer/salesforce.py +516 -342
  26. pyxecm/customizer/sap.py +58 -41
  27. pyxecm/customizer/servicenow.py +593 -371
  28. pyxecm/customizer/settings.py +442 -0
  29. pyxecm/customizer/successfactors.py +408 -346
  30. pyxecm/customizer/translate.py +83 -48
  31. pyxecm/helper/__init__.py +5 -2
  32. pyxecm/helper/assoc.py +83 -43
  33. pyxecm/helper/data.py +2406 -870
  34. pyxecm/helper/logadapter.py +27 -0
  35. pyxecm/helper/web.py +229 -101
  36. pyxecm/helper/xml.py +527 -171
  37. pyxecm/maintenance_page/__init__.py +5 -0
  38. pyxecm/maintenance_page/__main__.py +6 -0
  39. pyxecm/maintenance_page/app.py +51 -0
  40. pyxecm/maintenance_page/settings.py +28 -0
  41. pyxecm/maintenance_page/static/favicon.avif +0 -0
  42. pyxecm/maintenance_page/templates/maintenance.html +165 -0
  43. pyxecm/otac.py +234 -140
  44. pyxecm/otawp.py +1436 -557
  45. pyxecm/otcs.py +7716 -3161
  46. pyxecm/otds.py +2150 -919
  47. pyxecm/otiv.py +36 -21
  48. pyxecm/otmm.py +1272 -325
  49. pyxecm/otpd.py +231 -127
  50. pyxecm-2.0.0.dist-info/METADATA +145 -0
  51. pyxecm-2.0.0.dist-info/RECORD +54 -0
  52. {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/WHEEL +1 -1
  53. pyxecm-1.6.dist-info/METADATA +0 -53
  54. pyxecm-1.6.dist-info/RECORD +0 -32
  55. {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info/licenses}/LICENSE +0 -0
  56. {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/top_level.txt +0 -0
@@ -1,47 +1,7 @@
1
- """[Automate OpenText Directory Services (OTDS) and Extended ECM (OTCS) configurations]
2
-
3
- Data classes to handle settings read from environment variables
4
- * CustomizerSettings: Class to manage settings
5
- * CustomizerSettingsOTDS: Class for OTDS related settings
6
- * CustomizerSettingsOTCS: Class for OTCS related settings
7
- * CustomizerSettingsOTAC: Class for OTAC related settings
8
- * CustomizerSettingsOTPD: Class for OTPD related settings
9
- * CustomizerSettingsOTIV: Class for OTIV related settings
10
- * CustomizerSettingsK8S: Class for K8s related settings
11
- * CustomizerSettingsOTAWP: Class for OTAWP related settings
12
- * CustomizerSettingsM365: Class for O365 related settings
13
- * CustomizerSettingsAviator: Class for Aviator related settings
14
-
15
- Methods of class Customizer:
16
-
17
- __init__: object initializer for class Customizer
18
- log_header: Helper method to output a section header in the log file
19
- init_browser_automation: initialize browser automation for Content Aviator
20
- init_m365: initialize the Microsoft 365 object
21
- init_k8s: initialize the Kubernetes object we use to talk to the Kubernetes API
22
- init_otds: initialize the OTDS object
23
- init_otac: initialize the OTAC object
24
- init_otcs: initialize the OTCS (Extended ECM) object
25
- init_otiv: initialize the OTIV (Intelligent Viewing) object and its OTDS settings
26
- init_otpd: initialize the PowerDocs object
27
- init_otawp: initialize OTDS settings for AppWorks Platform
28
-
29
- restart_otcs_service: restart the OTCS backend and frontend pods -
30
- required to make certain configurations effective
31
- restart_otac_service: restart spawner process in Archive Center
32
- restart_otawp_pod: restart the AppWorks Platform Pod to make settings effective
33
- consolidate_otds: consolidate OTDS users / groups (to get to a fully synchronized state)
34
-
35
- import_powerdocs_configuration: import PowerDocs database
36
-
37
- set_maintenance_mode: Enable or Disable Maintenance Mode
38
-
39
- customization_run: Central function to initiate the customization
40
-
41
- """
1
+ """Module to automate Directory Services (OTDS) and Content Server (OTCS) configurations."""
42
2
 
43
3
  __author__ = "Dr. Marc Diefenbruch"
44
- __copyright__ = "Copyright 2024, OpenText"
4
+ __copyright__ = "Copyright (C) 2024-2025, OpenText"
45
5
  __credits__ = ["Kai-Philip Gatzweiler"]
46
6
  __maintainer__ = "Dr. Marc Diefenbruch"
47
7
  __email__ = "mdiefenb@opentext.com"
@@ -49,298 +9,58 @@ __email__ = "mdiefenb@opentext.com"
49
9
  import logging
50
10
  import os
51
11
  import sys
12
+ import tempfile
52
13
  import time
53
- from dataclasses import dataclass, field
54
- from datetime import datetime
55
- import uuid
56
- import xml.etree.ElementTree as ET
57
- import json
58
- import re
59
-
60
- # from packaging.version import Version
14
+ from datetime import datetime, timezone
15
+ from typing import TYPE_CHECKING
61
16
 
62
17
  import requests
63
18
 
64
19
  # OpenText specific modules:
65
20
  import yaml
66
- from pyxecm import OTAC, OTCS, OTDS, OTIV, OTPD, OTMM, CoreShare, OTAWP
67
- from pyxecm.avts import AVTS
21
+ from pydantic import HttpUrl
22
+
23
+ from pyxecm import AVTS, OTAC, OTAWP, OTCS, OTDS, OTIV, OTPD, CoreShare
68
24
  from pyxecm.customizer.k8s import K8s
69
25
  from pyxecm.customizer.m365 import M365
70
26
  from pyxecm.customizer.payload import Payload
27
+ from pyxecm.customizer.settings import Settings
71
28
 
72
- from pyxecm.customizer.browser_automation import BrowserAutomation
73
-
74
- logger = logging.getLogger("pyxecm.customizer")
75
-
76
-
77
- @dataclass
78
- class CustomizerSettings:
79
- """Class to manage settings"""
80
-
81
- placeholder_values: dict = field(default_factory=dict)
82
- stop_on_error: bool = os.environ.get("STOP_ON_ERROR", "false").lower() == "true"
83
- cust_log_file: str = "/tmp/customizing.log"
84
- customizer_start_time = customizer_end_time = datetime.now()
85
-
86
- # The following CUST artifacts are created by the main.tf in the python module:
87
- cust_settings_dir: str = "/settings/"
88
- cust_payload_dir: str = "/payload/"
89
- cust_payload: str = cust_payload_dir + "payload.yaml"
90
- cust_payload_gz: str = cust_payload_dir + "payload.yml.gz.b64"
91
- cust_payload_external: str = "/payload-external/"
92
-
93
- cust_target_folder_nickname: str = (
94
- "deployment" # nickname of folder to upload payload and log files
95
- )
96
- # CUST_RM_SETTINGS_DIR = "/opt/opentext/cs/appData/supportasset/Settings/"
97
- cust_rm_settings_dir = cust_settings_dir
98
-
99
-
100
- @dataclass
101
- class CustomizerSettingsOTDS:
102
- """Class for OTDS related settings"""
103
-
104
- protocol: str = os.environ.get("OTDS_PROTOCOL", "http")
105
- public_protocol: str = os.environ.get("OTDS_PUBLIC_PROTOCOL", "https")
106
- hostname: str = os.environ.get("OTDS_HOSTNAME", "otds")
107
- port: int = os.environ.get("OTDS_SERVICE_PORT_OTDS", 80)
108
- username: str = os.environ.get("OTDS_ADMIN", "admin")
109
- otds_ticket: str | None = None
110
- admin_partition: str = "otds.admin"
111
- public_url: str = os.environ.get("OTDS_PUBLIC_URL")
112
- password: str = os.environ.get("OTDS_PASSWORD")
113
- bindPassword: str = os.environ.get("BINB_PASSWORD")
114
- disable_password_policy: bool = True
115
- enable_audit: bool = True
116
-
117
-
118
- @dataclass
119
- class CustomizerSettingsOTCS:
120
- """Class for OTCS related settings"""
121
-
122
- # Content Server Constants:
123
- protocol: str = os.environ.get("OTCS_PROTOCOL", "http")
124
- public_protocol: str = os.environ.get("OTCS_PUBLIC_PROTOCOL", "https")
125
- hostname: str = os.environ.get("OTCS_HOSTNAME", "otcs-admin-0")
126
- hostname_backend: str = os.environ.get("OTCS_HOSTNAME", "otcs-admin-0")
127
- hostname_frontend: str = os.environ.get("OTCS_HOSTNAME_FRONTEND", "otcs-frontend")
128
- public_url: str = os.environ.get("OTCS_PUBLIC_URL", "otcs.public-url.undefined")
129
- port: int = os.environ.get("OTCS_SERVICE_PORT_OTCS", 8080)
130
- port_backend: int = os.environ.get("OTCS_SERVICE_PORT_OTCS", 8080)
131
- port_frontend: int = 80
132
- base_path: str = "/cs/cs"
133
- feme_uri: str = os.environ.get("FEME_URI", "ws://feme:4242")
134
- admin: str = os.environ.get("OTCS_ADMIN", "admin")
135
- password: str = os.environ.get("OTCS_PASSWORD")
136
- partition: str = os.environ.get("OTCS_PARTITION", "Content Server Members")
137
- resource_name: str = "cs"
138
- k8s_statefulset_frontend: str = "otcs-frontend"
139
- k8s_statefulset_backend: str = "otcs-admin"
140
- k8s_ingress: str = "otxecm-ingress"
141
- maintenance_mode: bool = (
142
- os.environ.get("OTCS_MAINTENANCE_MODE", "true").lower() == "true"
143
- )
144
- license_feature: str = "X3"
145
-
146
- # K8s service name and port for maintenance pod
147
- maintenance_service_name: str = "otxecm-customizer"
148
- mainteance_service_port: int = 5555
149
-
150
- replicas_frontend = 0
151
- replicas_backend = 0
152
-
153
- # Add configuration options for Customizer behaviour
154
- update_admin_user: bool = True
155
- upload_config_files: bool = True
156
- upload_status_files: bool = True
157
- upload_log_file: bool = True
158
-
159
-
160
- @dataclass
161
- class CustomizerSettingsOTAC:
162
- """Class for OTAC related settings"""
163
-
164
- enabled: bool = os.environ.get("OTAC_ENABLED", "false").lower() == "true"
165
- hostname: str = os.environ.get("OTAC_SERVICE_HOST", "otac-0")
166
- port: int = os.environ.get("OTAC_SERVICE_PORT", 8080)
167
- protocol: str = os.environ.get("OTAC_PROTOCOL", "http")
168
- public_url: str = os.environ.get("OTAC_PUBLIC_URL")
169
- admin: str = os.environ.get("OTAC_ADMIN", "dsadmin")
170
- password: str = os.environ.get("OTAC_PASSWORD", "")
171
- known_server: str = os.environ.get("OTAC_KNOWN_SERVER", "")
172
- k8s_pod_name: str = "otac-0"
173
-
174
-
175
- @dataclass
176
- class CustomizerSettingsOTPD:
177
- """Class for OTPD related settings"""
178
-
179
- enabled: bool = os.environ.get("OTPD_ENABLED", "false").lower() == "true"
180
- hostname: str = os.environ.get("OTPD_SERVICE_HOST", "otpd")
181
- port: int = os.environ.get("OTPD_SERVICE_PORT", 8080)
182
- protocol: str = os.environ.get("OTPD_PROTOCOL", "http")
183
- db_importfile: str = os.environ.get(
184
- "OTPD_DBIMPORTFILE", "URL://url.download.location/file.zip"
185
- )
186
- tenant: str = os.environ.get("OTPD_TENANT", "Successfactors")
187
- user: str = os.environ.get("OTPD_USER", "powerdocsapiuser")
188
- password: str = os.environ.get(
189
- "OTPD_PASSWORD",
190
- )
191
- k8s_pod_name: str = "otpd-0"
192
-
193
-
194
- @dataclass
195
- class CustomizerSettingsOTIV:
196
- """Class for OTIV related settings"""
197
-
198
- enabled: bool = os.environ.get("OTIV_ENABLED", "false").lower() == "true"
199
- license_file: str = "/payload/otiv-license.lic"
200
- license_feature: str = "FULLTIME_USERS_REGULAR"
201
- product_name: str = "Viewing"
202
- product_description: str = "OpenText Intelligent Viewing"
203
- resource_name: str = "iv"
204
-
205
-
206
- @dataclass
207
- class CustomizerSettingsK8S:
208
- """Class for K8s related settings"""
209
-
210
- enabled: bool = os.environ.get("K8S_ENABLED", "true").lower() == "true"
211
- in_cluster: bool = True
212
- kubeconfig_file: str = "~/.kube/config"
213
- namespace: str = "default"
214
-
215
-
216
- @dataclass
217
- class CustomizerSettingsOTAWP:
218
- """Class for OTAWP related settings"""
219
-
220
- enabled: bool = os.environ.get("OTAWP_ENABLED", "false").lower() == "true"
221
- license_file: str = "/payload/otawp-license.lic"
222
- product_name: str = "APPWORKS_PLATFORM"
223
- product_description: str = "OpenText Appworks Platform"
224
- resource_name: str = "awp"
225
- access_role_name: str = "Access to " + resource_name
226
- admin: str = os.environ.get("OTAWP_ADMIN", "sysadmin")
227
- password: str = os.environ.get("OTCS_PASSWORD")
228
- public_protocol: str = os.environ.get("OTAWP_PROTOCOL", "https")
229
- public_url: str = os.environ.get("OTAWP_PUBLIC_URL")
230
- k8s_statefulset: str = "appworks"
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")
234
-
235
-
236
- @dataclass
237
- class CustomizerSettingsM365:
238
- """Class for O365 related settings"""
239
-
240
- enabled: bool = os.environ.get("O365_ENABLED", "false").lower() == "true"
241
- tenant_id: str = os.environ.get("O365_TENANT_ID", "")
242
- client_id: str = os.environ.get("O365_CLIENT_ID", "")
243
- client_secret: str = os.environ.get("O365_CLIENT_SECRET", "")
244
- user: str = os.environ.get("O365_USER", "")
245
- password: str = os.environ.get("O365_PASSWORD", "")
246
- domain: str = os.environ.get("O365_DOMAIN", "")
247
- sku_id: str = os.environ.get("O365_SKU_ID", "c7df2760-2c81-4ef7-b578-5b5392b571df")
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", "")
265
-
266
-
267
- @dataclass
268
- class CustomizerSettingsAviator:
269
- """Class for Aviator related settings"""
270
-
271
- enabled: bool = os.environ.get("AVIATOR_ENABLED", "false").lower() == "true"
272
-
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", "")
29
+ if TYPE_CHECKING:
30
+ from pyxecm.customizer.browser_automation import BrowserAutomation
31
+
32
+ default_logger = logging.getLogger("pyxecm.customizer")
285
33
 
286
34
 
287
35
  class Customizer:
288
- """Customizer Class to control the cusomization automation
36
+ """Customizer Class to control the cusomization automation."""
289
37
 
290
- Args: None
291
- """
38
+ logger: logging.Logger = default_logger
39
+ customizer_start_time: datetime | None
40
+ customizer_stop_time: datetime | None
292
41
 
293
42
  def __init__(
294
43
  self,
295
- settings: CustomizerSettings = CustomizerSettings(),
296
- otds: CustomizerSettingsOTDS = CustomizerSettingsOTDS(),
297
- otcs: CustomizerSettingsOTCS = CustomizerSettingsOTCS(),
298
- otac: CustomizerSettingsOTAC = CustomizerSettingsOTAC(),
299
- otpd: CustomizerSettingsOTPD = CustomizerSettingsOTPD(),
300
- otiv: CustomizerSettingsOTIV = CustomizerSettingsOTIV(),
301
- k8s: CustomizerSettingsK8S = CustomizerSettingsK8S(),
302
- otawp: CustomizerSettingsOTAWP = CustomizerSettingsOTAWP(),
303
- m365: CustomizerSettingsM365 = CustomizerSettingsM365(),
304
- core_share: CustomizerSettingsCoreShare = CustomizerSettingsCoreShare(),
305
- aviator: CustomizerSettingsAviator = CustomizerSettingsAviator(),
306
- avts: CustomizerSettingsAVTS = CustomizerSettingsAVTS(),
307
- ):
308
- self.settings = settings
309
-
310
- # OTDS Constants:
311
- self.otds_settings = otds
312
-
313
- # Content Server Constants:
314
- self.otcs_settings = otcs
315
-
316
- # Archive Center constants:
317
- self.otac_settings = otac
318
-
319
- # PowerDocs constants:
320
- self.otpd_settings = otpd
44
+ settings: dict | None = None,
45
+ logger: logging.Logger = default_logger,
46
+ ) -> None:
47
+ """Initialize Customzer object.
321
48
 
322
- # Intelligent Viewing constants:
323
- self.otiv_settings = otiv
324
-
325
- # AppWorks Platform constants:
326
- self.otawp_settings = otawp
327
-
328
- # K8s Mode
329
- self.k8s_settings = k8s
330
-
331
- # Microsoft 365 Environment variables:
332
- self.m365_settings = m365
49
+ Args:
50
+ settings (dict | None, optional):
51
+ Customizer settings. Defaults to None.
52
+ logger (logging.Logger, optional):
53
+ The loggoing object to be used for all log messages.
54
+ Defaults to default_logger.
333
55
 
334
- # Core Share Environment variables:
335
- self.core_share_settings = core_share
56
+ """
336
57
 
337
- # Aviator variables:
338
- self.aviator_settings = aviator
58
+ self.logger = logger
339
59
 
340
- # Aviator Search variables:
341
- self.avts_settings = avts
60
+ # Create Settings class, raise ValidationError if settings are invalid
61
+ self.settings = Settings(**settings) if settings is not None else Settings()
342
62
 
343
- # Initialize Objects for later assignment
63
+ # Initialize Objects:
344
64
  self.otds_object: OTDS | None = None
345
65
  self.otcs_object: OTCS | None = None
346
66
  self.otcs_backend_object: OTCS | None = None
@@ -353,23 +73,30 @@ class Customizer:
353
73
  self.core_share_object: CoreShare | None = None
354
74
  self.browser_automation_object: BrowserAutomation | None = None
355
75
  self.otawp_object: OTAWP | None = None
76
+ self.avts_object: AVTS | None = None
356
77
 
357
78
  # end initializer
358
79
 
359
- def log_header(self, text: str, char: str = "=", length: int = 80):
360
- """Helper method to output a section header in the log file
80
+ def log_header(self, text: str, char: str = "=", length: int = 120) -> None:
81
+ """Output a section header in the log file.
361
82
 
362
83
  Args:
363
- text (str): Headline text to output into the log file.
364
- char (str, optional): header line character. Defaults to "=".
365
- length (int, optional): maxium length. Defaults to 80.
84
+ text (str):
85
+ Headline text to output into the log file.
86
+ char (str, optional):
87
+ The header line character. Defaults to "=".
88
+ length (int, optional):
89
+ The maximum line length. Defaults to 120.
90
+
366
91
  Returns:
367
92
  None
93
+
368
94
  """
369
95
 
370
96
  # Calculate the remaining space for the text after adding spaces
371
97
  available_space = max(
372
- 0, length - len(text) - 2
98
+ 0,
99
+ length - len(text) - 2,
373
100
  ) # 2 accounts for the spaces each side of the text
374
101
 
375
102
  # Calculate the number of characters needed on each side
@@ -380,8 +107,11 @@ class Customizer:
380
107
  char_count = max(3, char_count)
381
108
 
382
109
  # Build the header string, extra_char is either 0 or 1
383
- logger.info(
384
- "%s %s %s", char * char_count, text, char * (char_count + extra_char)
110
+ self.logger.info(
111
+ "%s %s %s",
112
+ char * char_count,
113
+ text,
114
+ char * (char_count + extra_char),
385
115
  )
386
116
 
387
117
  # end method definition
@@ -391,235 +121,248 @@ class Customizer:
391
121
 
392
122
  Args:
393
123
  None
124
+
394
125
  Returns:
395
- object: M365 object or None if the object couldn't be created or
396
- the authentication fails.
126
+ M365 object:
127
+ M365 object or None if the object couldn't be created or
128
+ the authentication fails.
129
+
397
130
  """
398
131
 
399
- logger.info(
400
- "Microsoft 365 Tenant ID = %s", self.m365_settings.tenant_id
401
- )
402
- logger.info(
403
- "Microsoft 365 Client ID = %s", self.m365_settings.client_id
132
+ self.logger.info(
133
+ "Microsoft 365 Tenant ID = %s",
134
+ self.settings.m365.tenant_id,
404
135
  )
405
- logger.debug(
406
- "Microsoft 365 Client Secret = %s", self.m365_settings.client_secret
136
+ self.logger.debug(
137
+ "Microsoft 365 Client ID = %s",
138
+ self.settings.m365.client_id,
407
139
  )
408
- logger.info(
409
- "Microsoft 365 User = %s",
410
- (
411
- self.m365_settings.user
412
- if self.m365_settings.user != ""
413
- else "<not configured>"
414
- ),
415
- )
416
- logger.debug(
417
- "Microsoft 365 Password = %s",
418
- (
419
- self.m365_settings.password
420
- if self.m365_settings.password != ""
421
- else "<not configured>"
422
- ),
140
+ self.logger.debug(
141
+ "Microsoft 365 Client Secret = %s",
142
+ self.settings.m365.client_secret,
423
143
  )
424
- logger.info(
425
- "Microsoft 365 Domain = %s", self.m365_settings.domain
144
+ self.logger.info(
145
+ "Microsoft 365 Domain = %s",
146
+ self.settings.m365.domain,
426
147
  )
427
- logger.info(
428
- "Microsoft 365 Default License SKU = %s", self.m365_settings.sku_id
148
+ self.logger.info(
149
+ "Microsoft 365 Default License SKU = %s",
150
+ self.settings.m365.sku_id,
429
151
  )
430
- logger.info(
152
+ self.logger.info(
431
153
  "Microsoft 365 Teams App Name = %s",
432
- self.m365_settings.teams_app_name,
154
+ self.settings.m365.teams_app_name,
433
155
  )
434
- logger.info(
156
+ self.logger.info(
435
157
  "Microsoft 365 Teams App External ID = %s",
436
- self.m365_settings.teams_app_external_id,
158
+ self.settings.m365.teams_app_external_id,
159
+ )
160
+ self.logger.info(
161
+ "Microsoft 365 SharePoint App Root Site = %s",
162
+ self.settings.m365.sharepoint_app_root_site,
163
+ )
164
+ self.logger.info(
165
+ "Microsoft 365 SharePoint App Client ID = %s",
166
+ self.settings.m365.sharepoint_app_client_id,
167
+ )
168
+ self.logger.debug(
169
+ "Microsoft 365 SharePoint App Client Secret = %s",
170
+ self.settings.m365.sharepoint_app_client_secret,
437
171
  )
438
172
 
439
173
  m365_object = M365(
440
- tenant_id=self.m365_settings.tenant_id,
441
- client_id=self.m365_settings.client_id,
442
- client_secret=self.m365_settings.client_secret,
443
- domain=self.m365_settings.domain,
444
- sku_id=self.m365_settings.sku_id,
445
- teams_app_name=self.m365_settings.teams_app_name,
446
- teams_app_external_id=self.m365_settings.teams_app_external_id,
174
+ tenant_id=self.settings.m365.tenant_id,
175
+ client_id=self.settings.m365.client_id,
176
+ client_secret=self.settings.m365.client_secret,
177
+ domain=self.settings.m365.domain,
178
+ sku_id=self.settings.m365.sku_id,
179
+ teams_app_name=self.settings.m365.teams_app_name,
180
+ teams_app_external_id=self.settings.m365.teams_app_external_id,
181
+ sharepoint_app_root_site=self.settings.m365.sharepoint_app_root_site,
182
+ sharepoint_app_client_id=self.settings.m365.sharepoint_app_client_id,
183
+ sharepoint_app_client_secret=self.settings.m365.sharepoint_app_client_secret,
184
+ logger=self.logger,
447
185
  )
448
186
 
449
187
  if m365_object and m365_object.authenticate():
450
- logger.info("Connected to Microsoft Graph API.")
188
+ self.logger.info("Connected to Microsoft Graph API.")
451
189
  else:
452
- logger.error("Failed to connect to Microsoft Graph API.")
190
+ self.logger.error("Failed to connect to Microsoft Graph API.")
453
191
  return m365_object
454
192
 
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
193
+ # Check if the Teams App should be updated, we don't do this always due to the bug described below
194
+ if self.settings.m365.update_teams_app:
195
+ self.logger.info(
196
+ "Download M365 Teams App -> '%s' (external ID = %s) from Extended ECM (OTCS)...",
197
+ self.settings.m365.teams_app_name,
198
+ self.settings.m365.teams_app_external_id,
199
+ )
200
+
201
+ # Download MS Teams App from OTCS (this has with 23.2 a nasty side-effect
202
+ # of unsetting 2 checkboxes on that config page - we reset these checkboxes
203
+ # with the settings file "O365Settings.xml"):
204
+ file_path = os.path.join(tempfile.gettempdir(), "ot.xecm.teams.zip")
205
+ response = self.otcs_frontend_object.download_config_file(
206
+ otcs_url_suffix="/cs/cs?func=officegroups.DownloadTeamsPackage",
207
+ file_path=file_path,
208
+ )
209
+
210
+ # Check if the app is already installed in the apps catalog
211
+ # ideally we want to use the
212
+ app_exist = False
213
+
214
+ # If the App External ID is provided via Env variable then we
215
+ # prefer to use it instead of the App name:
216
+ if self.settings.m365.teams_app_external_id:
217
+ self.logger.info(
218
+ "Check if M365 Teams App -> '%s' (%s) is already installed in catalog using external app ID...",
219
+ self.settings.m365.teams_app_name,
220
+ self.settings.m365.teams_app_external_id,
488
221
  )
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,
222
+ response = m365_object.get_teams_apps(
223
+ filter_expression="externalId eq '{}'".format(
224
+ self.settings.m365.teams_app_external_id,
225
+ ),
504
226
  )
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
227
+ # this should always be True as ID is unique:
228
+ app_exist = m365_object.exist_result_item(
229
+ response=response,
230
+ key="externalId",
231
+ value=self.settings.m365.teams_app_external_id,
512
232
  )
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,
233
+ # If the app could not be found via the external ID we fall back to
234
+ # search for the app by name:
235
+ if not app_exist:
236
+ if self.settings.m365.teams_app_external_id:
237
+ self.logger.info(
238
+ "Could not find M365 Teams App by external ID -> %s. Try to lookup the app by name -> '%s' instead...",
239
+ self.settings.m365.teams_app_external_id,
240
+ self.settings.m365.teams_app_name,
241
+ )
242
+ self.logger.info(
243
+ "Check if M365 Teams App -> '%s' is already installed in catalog (using app name)...",
244
+ self.settings.m365.teams_app_name,
531
245
  )
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...",
246
+ response = m365_object.get_teams_apps(
247
+ filter_expression="contains(displayName, '{}')".format(
248
+ self.settings.m365.teams_app_name,
249
+ ),
250
+ )
251
+ app_exist = m365_object.exist_result_item(
252
+ response=response,
253
+ key="displayName",
254
+ value=self.settings.m365.teams_app_name,
255
+ )
256
+ if app_exist:
257
+ # We double check that we have the effective name of the app
258
+ # in the catalog to avoid errors when the app is looked up
259
+ # by its wrong name in the customizer automation. This can
260
+ # happen if the app is installed manually or the environment
261
+ # variable is set to a wrong name.
262
+ app_catalog_name = m365_object.get_result_value(response, "displayName")
263
+ if app_catalog_name != self.settings.m365.teams_app_name:
264
+ self.logger.warning(
265
+ "The Extended ECM app name -> '%s' in the M365 Teams catalog does not match the defined app name -> '%s'!",
266
+ app_catalog_name,
267
+ self.settings.m365.teams_app_name,
268
+ )
269
+ # Align the name in the settings dict with the existing name in the catalog.
270
+ self.settings.m365.teams_app_name = app_catalog_name
271
+ # Align the name in the M365 object config dict with the existing name in the catalog.
272
+ m365_object.config()["teamsAppName"] = app_catalog_name
273
+ app_internal_id = m365_object.get_result_value(
274
+ response=response,
275
+ key="id",
276
+ index=0,
277
+ ) # 0 = Index = first item
278
+ # Store the internal ID for later use
279
+ m365_object.config()["teamsAppInternalId"] = app_internal_id
280
+ app_catalog_version = m365_object.get_result_value(
281
+ response=response,
282
+ key="version",
283
+ index=0,
284
+ sub_dict_name="appDefinitions",
285
+ )
286
+ self.logger.info(
287
+ "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...",
288
+ self.settings.m365.teams_app_name,
289
+ self.settings.m365.teams_app_external_id,
290
+ app_internal_id,
560
291
  app_catalog_version,
561
- app_download_version,
562
292
  )
293
+ app_path = os.path.join(tempfile.gettempdir(), "ot.xecm.teams.zip")
294
+ app_download_version = m365_object.extract_version_from_app_manifest(
295
+ app_path=app_path,
296
+ )
297
+ if app_catalog_version < app_download_version:
298
+ self.logger.info(
299
+ "Upgrading Extended ECM Teams App in catalog from version -> %s to version -> %s...",
300
+ app_catalog_version,
301
+ app_download_version,
302
+ )
303
+ app_path = os.path.join(tempfile.gettempdir(), "ot.xecm.teams.zip")
304
+ response = m365_object.upload_teams_app(
305
+ app_path=app_path,
306
+ update_existing_app=True,
307
+ app_catalog_id=app_internal_id,
308
+ )
309
+ app_internal_id = m365_object.get_result_value(
310
+ response=response,
311
+ key="teamsAppId",
312
+ )
313
+ if app_internal_id:
314
+ self.logger.info(
315
+ "Successfully upgraded Extended ECM Teams App -> '%s' (external ID = %s). Internal App ID -> %s",
316
+ self.settings.m365.teams_app_name,
317
+ self.settings.m365.teams_app_external_id,
318
+ app_internal_id,
319
+ )
320
+ # Store the internal ID for later use
321
+ m365_object.config()["teamsAppInternalId"] = app_internal_id
322
+ else:
323
+ self.logger.error(
324
+ "Failed to upgrade Extended ECM Teams App -> '%s' (external ID = %s).",
325
+ self.settings.m365.teams_app_name,
326
+ self.settings.m365.teams_app_external_id,
327
+ )
328
+ else:
329
+ self.logger.info(
330
+ "No upgrade required. The downloaded version -> %s is not newer than the version -> %s which is already in the M365 app catalog.",
331
+ app_download_version,
332
+ app_catalog_version,
333
+ )
334
+ else: # Extended ECM M365 Teams app is not yet installed...
335
+ self.logger.info(
336
+ "Extended Teams ECM App -> '%s' (external ID = %s) is not yet in app catalog. Installing as new app...",
337
+ self.settings.m365.teams_app_name,
338
+ self.settings.m365.teams_app_external_id,
339
+ )
340
+ app_path = os.path.join(tempfile.gettempdir(), "ot.xecm.teams.zip")
563
341
  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,
342
+ app_path=app_path,
343
+ update_existing_app=False,
567
344
  )
568
345
  app_internal_id = m365_object.get_result_value(
569
346
  response=response,
570
- key="teamsAppId",
347
+ key="id", # for new installs it is NOT "teamsAppId" but "id" as we use a different M365 Graph API endpoint !!!
571
348
  )
572
349
  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,
350
+ self.logger.info(
351
+ "Successfully installed Extended ECM Teams App -> '%s' (external ID = %s). Internal App ID -> %s",
352
+ self.settings.m365.teams_app_name,
353
+ self.settings.m365.teams_app_external_id,
577
354
  app_internal_id,
578
355
  )
579
356
  # Store the internal ID for later use
580
357
  m365_object.config()["teamsAppInternalId"] = app_internal_id
581
358
  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,
359
+ self.logger.error(
360
+ "Failed to install Extended ECM Teams App -> '%s' (external ID = %s).",
361
+ self.settings.m365.teams_app_name,
362
+ self.settings.m365.teams_app_external_id,
586
363
  )
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
364
 
622
- # logger.info("======== Upload Outlook Add-In ============")
365
+ # self.logger.info("======== Upload Outlook Add-In ============")
623
366
 
624
367
  # # Download MS Outlook Add-In from OTCS:
625
368
  # MANIFEST_FILE = "/tmp/BusinessWorkspace.Manifest.xml"
@@ -627,13 +370,13 @@ class Customizer:
627
370
  # "/cs/cs?func=outlookaddin.DownloadManifest",
628
371
  # MANIFEST_FILE,
629
372
  # "DeployedContentServer",
630
- # self.otcs_settings.public_url,
373
+ # self.settings.otcs.public_url,
631
374
  # ):
632
- # logger.error("Failed to download M365 Outlook Add-In from Extended ECM!")
375
+ # self.logger.error("Failed to download M365 Outlook Add-In from Extended ECM!")
633
376
  # else:
634
377
  # # THIS IS NOT IMPLEMENTED DUE TO LACK OF M365 GRAPH API SUPPORT!
635
378
  # # 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)
379
+ # self.logger.info("Successfully downloaded M365 Outlook Add-In from Extended ECM to %s", MANIFEST_FILE)
637
380
  # m365_object.upload_outlook_app(MANIFEST_FILE)
638
381
 
639
382
  return m365_object
@@ -645,43 +388,49 @@ class Customizer:
645
388
 
646
389
  Args:
647
390
  None
391
+
648
392
  Returns:
649
- object: CoreShare object or None if the object couldn't be created or
650
- the authentication fails.
393
+ AVTS object:
394
+ Aviator Search object or None if the object couldn't be created or
395
+ the authentication fails.
396
+
651
397
  """
652
398
 
653
- logger.info(
654
- "Aviator Search Base URL = %s", self.avts_settings.base_url
399
+ self.logger.info(
400
+ "Aviator Search Base URL = %s",
401
+ self.settings.avts.base_url,
655
402
  )
656
- logger.info(
657
- "Aviator Search OTDS URL = %s", self.avts_settings.otds_url
403
+ self.logger.info(
404
+ "Aviator Search OTDS URL = %s",
405
+ self.settings.avts.otds_url,
658
406
  )
659
- logger.info(
660
- "Aviator Search Client ID = %s", self.avts_settings.client_id
407
+ self.logger.info(
408
+ "Aviator Search Client ID = %s",
409
+ self.settings.avts.client_id,
661
410
  )
662
- logger.debug(
411
+ self.logger.debug(
663
412
  "Aviator Search Client Secret = %s",
664
- self.avts_settings.client_secret,
413
+ self.settings.avts.client_secret,
665
414
  )
666
- logger.info(
667
- "Aviator Search User ID = %s", self.avts_settings.username
415
+ self.logger.info(
416
+ "Aviator Search User ID = %s",
417
+ self.settings.avts.username,
668
418
  )
669
- logger.debug(
419
+ self.logger.debug(
670
420
  "Aviator Search User Password = %s",
671
- self.avts_settings.password,
421
+ self.settings.avts.password,
672
422
  )
673
423
 
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,
424
+ return AVTS(
425
+ otds_url=str(self.settings.avts.otds_url),
426
+ base_url=str(self.settings.avts.base_url),
427
+ client_id=self.settings.avts.client_id,
428
+ client_secret=self.settings.avts.client_secret,
429
+ username=self.settings.avts.username,
430
+ password=self.settings.avts.password,
431
+ logger=self.logger,
681
432
  )
682
433
 
683
- return avts_object
684
-
685
434
  # end method definition
686
435
 
687
436
  def init_coreshare(self) -> CoreShare:
@@ -690,58 +439,56 @@ class Customizer:
690
439
  Args:
691
440
  None
692
441
  Returns:
693
- object: CoreShare object or None if the object couldn't be created or
694
- the authentication fails.
442
+ CoreShare object:
443
+ Core Share object or None if the object couldn't be created or
444
+ the authentication fails.
445
+
695
446
  """
696
447
 
697
- logger.info(
698
- "Core Share Base URL = %s", self.core_share_settings.base_url
448
+ self.logger.info(
449
+ "Core Share Base URL = %s",
450
+ self.settings.coreshare.base_url,
699
451
  )
700
- logger.info(
701
- "Core Share SSO URL = %s", self.core_share_settings.sso_url
452
+ self.logger.info(
453
+ "Core Share SSO URL = %s",
454
+ self.settings.coreshare.sso_url,
702
455
  )
703
- logger.info(
704
- "Core Share Client ID = %s", self.core_share_settings.client_id
456
+ self.logger.info(
457
+ "Core Share Client ID = %s",
458
+ self.settings.coreshare.client_id,
705
459
  )
706
- logger.debug(
460
+ self.logger.debug(
707
461
  "Core Share Client Secret = %s",
708
- self.core_share_settings.client_secret,
462
+ self.settings.coreshare.client_secret,
709
463
  )
710
- logger.info(
464
+ self.logger.info(
711
465
  "Core Share User = %s",
712
- (
713
- self.core_share_settings.username
714
- if self.core_share_settings.username != ""
715
- else "<not configured>"
716
- ),
466
+ (self.settings.coreshare.username if self.settings.coreshare.username != "" else "<not configured>"),
717
467
  )
718
- logger.debug(
468
+ self.logger.debug(
719
469
  "Core Share Password = %s",
720
- (
721
- self.core_share_settings.password
722
- if self.core_share_settings.password != ""
723
- else "<not configured>"
724
- ),
470
+ (self.settings.coreshare.password if self.settings.coreshare.password != "" else "<not configured>"),
725
471
  )
726
472
 
727
473
  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,
474
+ base_url=self.settings.coreshare.base_url,
475
+ sso_url=self.settings.coreshare.sso_url,
476
+ client_id=self.settings.coreshare.client_id,
477
+ client_secret=self.settings.coreshare.client_secret,
478
+ username=self.settings.coreshare.username,
479
+ password=self.settings.coreshare.password.get_secret_value(),
480
+ logger=self.logger,
734
481
  )
735
482
 
736
483
  if core_share_object and core_share_object.authenticate_admin():
737
- logger.info("Connected to Core Share as Tenant Admin.")
484
+ self.logger.info("Connected to Core Share as Tenant Admin.")
738
485
  else:
739
- logger.error("Failed to connect to Core Share as Tenant Admin.")
486
+ self.logger.error("Failed to connect to Core Share as Tenant Admin.")
740
487
 
741
488
  if core_share_object and core_share_object.authenticate_user():
742
- logger.info("Connected to Core Share as Tenant Service User.")
489
+ self.logger.info("Connected to Core Share as Tenant Service User.")
743
490
  else:
744
- logger.error("Failed to connect to Core Share as Tenant Service User.")
491
+ self.logger.error("Failed to connect to Core Share as Tenant Service User.")
745
492
 
746
493
  return core_share_object
747
494
 
@@ -752,64 +499,66 @@ class Customizer:
752
499
 
753
500
  Args:
754
501
  None
502
+
755
503
  Returns:
756
504
  K8s: K8s object
505
+
757
506
  Side effects:
758
507
  The global variables otcs_replicas_frontend and otcs_replicas_backend are initialized
508
+
759
509
  """
760
510
 
761
- logger.info("Connection parameters Kubernetes (K8s):")
762
- logger.info("K8s inCluster = %s", self.k8s_settings.in_cluster)
763
- logger.info("K8s namespace = %s", self.k8s_settings.namespace)
764
- logger.info(
511
+ self.logger.info("Connection parameters Kubernetes (K8s):")
512
+ self.logger.info("K8s namespace = %s", self.settings.k8s.namespace)
513
+ self.logger.info(
765
514
  "K8s kubeconfig file = %s",
766
- self.k8s_settings.kubeconfig_file,
515
+ self.settings.k8s.kubeconfig_file,
767
516
  )
768
517
 
769
518
  k8s_object = K8s(
770
- in_cluster=self.k8s_settings.in_cluster,
771
- kubeconfig_file=self.k8s_settings.kubeconfig_file,
772
- namespace=self.k8s_settings.namespace,
519
+ kubeconfig_file=self.settings.k8s.kubeconfig_file,
520
+ namespace=self.settings.k8s.namespace,
521
+ logger=self.logger,
773
522
  )
774
523
  if k8s_object:
775
- logger.info("Kubernetes API is ready now.")
524
+ self.logger.info("Kubernetes API is ready now.")
776
525
  else:
777
- logger.error("Cannot establish connection to Kubernetes.")
526
+ self.logger.error("Cannot establish connection to Kubernetes.")
778
527
 
779
528
  # Get number of replicas for frontend:
780
529
  otcs_frontend_scale = k8s_object.get_stateful_set_scale(
781
- self.otcs_settings.k8s_statefulset_frontend
530
+ sts_name=self.settings.k8s.sts_otcs_frontend,
782
531
  )
783
532
  if not otcs_frontend_scale:
784
- logger.error(
533
+ self.logger.error(
785
534
  "Cannot find Kubernetes Stateful Set -> '%s' for OTCS Frontends!",
786
- self.otcs_settings.k8s_statefulset_frontend,
535
+ self.settings.k8s.sts_otcs_frontend,
787
536
  )
788
537
  sys.exit()
789
538
 
790
- self.otcs_settings.replicas_frontend = otcs_frontend_scale.spec.replicas # type: ignore
791
- logger.info(
539
+ self.settings.k8s.sts_otcs_frontend_replicas = otcs_frontend_scale.spec.replicas
540
+ self.logger.info(
792
541
  "Stateful Set -> '%s' has -> %s replicas",
793
- self.otcs_settings.k8s_statefulset_frontend,
794
- self.otcs_settings.replicas_frontend,
542
+ self.settings.k8s.sts_otcs_frontend,
543
+ self.settings.k8s.sts_otcs_frontend_replicas,
795
544
  )
796
545
 
797
546
  # Get number of replicas for backend:
798
547
  otcs_backend_scale = k8s_object.get_stateful_set_scale(
799
- self.otcs_settings.k8s_statefulset_backend
548
+ sts_name=self.settings.k8s.sts_otcs_admin,
800
549
  )
801
550
  if not otcs_backend_scale:
802
- logger.error(
551
+ self.logger.error(
803
552
  "Cannot find Kubernetes Stateful Set -> '%s' for OTCS Backends!",
804
- self.otcs_settings.k8s_statefulset_backend,
553
+ self.settings.k8s.sts_otcs_admin,
805
554
  )
806
555
  sys.exit()
807
556
 
808
- self.otcs_settings.replicas_backend = otcs_backend_scale.spec.replicas # type: ignore
809
- logger.info(
557
+ self.settings.k8s.sts_otcs_admin_replicas = otcs_backend_scale.spec.replicas
558
+ self.logger.info(
810
559
  "Stateful Set -> '%s' has -> %s replicas",
811
- self.otcs_settings.k8s_statefulset_backend,
812
- self.otcs_settings.replicas_backend,
560
+ self.settings.k8s.sts_otcs_admin,
561
+ self.settings.k8s.sts_otcs_admin_replicas,
813
562
  )
814
563
 
815
564
  return k8s_object
@@ -821,53 +570,67 @@ class Customizer:
821
570
 
822
571
  Args:
823
572
  None
573
+
824
574
  Returns:
825
- object: OTDS object
575
+ OTDS:
576
+ The OTDS object
577
+
826
578
  """
827
579
 
828
- logger.info("Connection parameters OTDS:")
829
- logger.info("OTDS Protocol = %s", self.otds_settings.protocol)
830
- logger.info("OTDS Public Protocol = %s", self.otds_settings.public_protocol)
831
- logger.info("OTDS Hostname = %s", self.otds_settings.hostname)
832
- logger.info("OTDS Public URL = %s", self.otds_settings.public_url)
833
- logger.info("OTDS Port = %s", str(self.otds_settings.port))
834
- logger.info("OTDS Admin User = %s", self.otds_settings.username)
835
- logger.debug("OTDS Admin Password = %s", self.otds_settings.password)
836
- logger.debug("OTDS Ticket = %s", self.otds_settings.otds_ticket)
837
- logger.info("OTDS Admin Partition = %s", self.otds_settings.admin_partition)
580
+ self.logger.info("Connection parameters OTDS:")
581
+ self.logger.info("OTDS Protocol = %s", self.settings.otds.url.scheme)
582
+ self.logger.info(
583
+ "OTDS Hostname = %s",
584
+ self.settings.otds.url_internal.host,
585
+ )
586
+ self.logger.info(
587
+ "OTDS Port = %s",
588
+ str(self.settings.otds.url.port),
589
+ )
590
+ self.logger.info("OTDS Public Protocol = %s", self.settings.otds.url.scheme)
591
+ self.logger.info("OTDS Public URL = %s", self.settings.otds.url.host)
592
+ self.logger.info("OTDS Public Port = %s", self.settings.otds.url.port)
593
+ self.logger.info("OTDS Admin User = %s", self.settings.otds.username)
594
+ self.logger.debug("OTDS Admin Password = %s", self.settings.otds.password)
595
+ self.logger.debug("OTDS Ticket = %s", self.settings.otds.ticket)
596
+ self.logger.info(
597
+ "OTDS Admin Partition = %s",
598
+ self.settings.otds.admin_partition,
599
+ )
838
600
 
839
601
  otds_object = OTDS(
840
- protocol=self.otds_settings.protocol,
841
- hostname=self.otds_settings.hostname,
842
- port=self.otds_settings.port,
843
- username=self.otds_settings.username,
844
- password=self.otds_settings.password,
845
- otds_ticket=self.otds_settings.otds_ticket,
846
- bindPassword=self.otds_settings.bindPassword
602
+ protocol=self.settings.otds.url_internal.scheme,
603
+ hostname=self.settings.otds.url_internal.host,
604
+ port=self.settings.otds.url_internal.port,
605
+ username=self.settings.otds.username,
606
+ password=self.settings.otds.password.get_secret_value(),
607
+ otds_ticket=self.settings.otds.ticket,
608
+ bind_password=self.settings.otds.bind_password.get_secret_value(),
609
+ logger=self.logger,
847
610
  )
848
611
 
849
- logger.info("Authenticating to OTDS...")
612
+ self.logger.info("Authenticating to OTDS...")
850
613
  otds_cookie = otds_object.authenticate()
851
614
  while otds_cookie is None:
852
- logger.warning("Waiting 30 seconds for OTDS to become ready...")
615
+ self.logger.info("Waiting 30 seconds for OTDS to become ready...")
853
616
  time.sleep(30)
854
617
  otds_cookie = otds_object.authenticate()
855
- logger.info("OTDS is ready now.")
618
+ self.logger.info("OTDS is ready now.")
856
619
 
857
- logger.info("Enable OTDS audit...")
620
+ self.logger.info("Enable OTDS audit...")
858
621
 
859
- if self.otds_settings.enable_audit:
622
+ if self.settings.otds.enable_audit:
860
623
  otds_object.enable_audit()
861
624
 
862
- if self.otds_settings.disable_password_policy:
863
- logger.info("Disable OTDS password expiry...")
625
+ if self.settings.otds.disable_password_policy:
626
+ self.logger.info("Disable OTDS password expiry...")
864
627
  # Setting the value to 0 disables password expiry.
865
628
  # The default is 90 days and we may have Terrarium
866
629
  # instances that are running longer than that. This
867
630
  # avoids problems with customerizer re-runs of
868
631
  # instances that are > 90 days old.
869
632
  otds_object.update_password_policy(
870
- update_values={"passwordMaximumDuration": 0}
633
+ update_values={"passwordMaximumDuration": 0},
871
634
  )
872
635
 
873
636
  return otds_object
@@ -876,76 +639,83 @@ class Customizer:
876
639
 
877
640
  def init_otac(self) -> OTAC:
878
641
  """Initialize the OTAC object and parameters.
879
- Configure the Archive Server as a known server
880
- if environment variable OTAC_KNOWN_SERVER is set.
642
+
643
+ Configure the Archive Server as a known server
644
+ if environment variable OTAC_KNOWN_SERVER is set.
881
645
 
882
646
  Args: None
883
- Return:
884
- OTAC object
647
+
648
+ Returns:
649
+ The OTAC object.
650
+
885
651
  """
886
652
 
887
- logger.info("Connection parameters OTAC:")
888
- logger.info("OTAC Protocol = %s", self.otac_settings.protocol)
889
- logger.info("OTAC Hostname = %s", self.otac_settings.hostname)
890
- logger.info("OTAC Public URL = %s", self.otac_settings.public_url)
891
- logger.info("OTAC Port = %s", str(self.otac_settings.port))
892
- logger.info("OTAC Admin User = %s", self.otac_settings.admin)
893
- logger.debug("OTAC Admin Password = %s", self.otac_settings.password)
894
- logger.info(
653
+ self.logger.info("Connection parameters OTAC:")
654
+ self.logger.info("OTAC URL = %s", str(self.settings.otac.url))
655
+ self.logger.info("OTAC URL internal = %s", str(self.settings.otac.url_internal))
656
+ self.logger.info("OTAC Admin User = %s", self.settings.otac.username)
657
+ self.logger.debug("OTAC Admin Password = %s", self.settings.otac.password)
658
+ self.logger.info(
895
659
  "OTAC Known Server = %s",
896
- (
897
- self.otac_settings.known_server
898
- if self.otac_settings.known_server != ""
899
- else "<not configured>"
900
- ),
660
+ (self.settings.otac.known_server if self.settings.otac.known_server != "" else "<not configured>"),
901
661
  )
902
662
 
903
663
  otac_object = OTAC(
904
- self.otac_settings.protocol,
905
- self.otac_settings.hostname,
906
- int(self.otac_settings.port),
907
- self.otac_settings.admin,
908
- self.otac_settings.password,
909
- self.otds_settings.username,
910
- self.otds_settings.password,
911
- )
664
+ self.settings.otac.url_internal.scheme,
665
+ self.settings.otac.url_internal.host,
666
+ int(self.settings.otac.url_internal.port),
667
+ self.settings.otac.username,
668
+ self.settings.otac.password.get_secret_value(),
669
+ self.settings.otds.username,
670
+ self.settings.otds.password.get_secret_value(),
671
+ logger=self.logger,
672
+ )
673
+
674
+ self.logger.info("Authenticating to OTAC...")
675
+ otac_cookie = otac_object.authenticate()
676
+ while otac_cookie is None:
677
+ self.logger.info("Waiting 30 seconds for OTAC to become ready...")
678
+ time.sleep(30)
679
+ otac_cookie = otac_object.authenticate()
680
+ self.logger.info("OTAC is ready now.")
912
681
 
913
682
  # This is a work-around as OTCS container automation is not
914
683
  # enabling the certificate reliable.
915
684
  response = otac_object.enable_certificate(
916
- cert_name="SP_otcs-admin-0", cert_type="ARC"
685
+ cert_name="SP_otcs-admin-0",
686
+ cert_type="ARC",
917
687
  )
918
688
  if not response:
919
- logger.error("Failed to enable OTAC certificate for Extended ECM!")
689
+ self.logger.error("Failed to enable OTAC certificate for Extended ECM!")
920
690
  else:
921
- logger.info("Successfully enabled OTAC certificate for Extended ECM!")
691
+ self.logger.info("Successfully enabled OTAC certificate for Extended ECM!")
922
692
 
923
693
  # is there a known server configured for Archive Center (to sync content with)
924
- if otac_object and self.otac_settings.known_server != "":
694
+ if otac_object and self.settings.otac.known_server != "":
925
695
  # wait until the OTAC pod is in ready state
926
- logger.info("Waiting for Archive Center to become ready...")
927
- self.k8s_object.wait_pod_condition(self.otac_settings.k8s_pod_name, "Ready")
696
+ self.logger.info("Waiting for Archive Center to become ready...")
697
+ self.k8s_object.wait_pod_condition(self.settings.k8s.pod_otac, "Ready")
928
698
 
929
- logger.info("Configure known host for Archive Center...")
699
+ self.logger.info("Configure known host for Archive Center...")
930
700
  response = otac_object.exec_command(
931
- f"cf_create_host {self.otac_settings.known_server} 0 /archive 8080 8090"
701
+ f"cf_create_host {self.settings.otac.known_server} 0 /archive 8080 8090",
932
702
  )
933
703
  if not response or not response.ok:
934
- logger.error("Failed to configure known host for Archive Center!")
704
+ self.logger.error("Failed to configure known host for Archive Center!")
935
705
 
936
- logger.info("Configure host alias for Archive Center...")
706
+ self.logger.info("Configure host alias for Archive Center...")
937
707
  response = otac_object.exec_command(
938
- f"cf_set_variable MY_HOST_ALIASES {self.otac_settings.k8s_pod_name},{self.otac_settings.public_url},otac DS"
708
+ f"cf_set_variable MY_HOST_ALIASES {self.settings.k8s.pod_otac},{self.settings.otac.url.host},otac DS",
939
709
  )
940
710
  if not response or not response.ok:
941
- logger.error("Failed to configure host alias for Archive Center!")
711
+ self.logger.error("Failed to configure host alias for Archive Center!")
942
712
 
943
713
  # Restart the spawner in Archive Center:
944
- logger.info("Restart Archive Center Spawner...")
714
+ self.logger.info("Restart Archive Center Spawner...")
945
715
  self.restart_otac_service()
946
716
  else:
947
- logger.info(
948
- "Skip configuration of known host for Archive Center (OTAC_KNOWN_SERVER is not set)."
717
+ self.logger.info(
718
+ "Skip configuration of known host for Archive Center (OTAC_KNOWN_SERVER is not set).",
949
719
  )
950
720
 
951
721
  return otac_object
@@ -954,116 +724,118 @@ class Customizer:
954
724
 
955
725
  def init_otcs(
956
726
  self,
957
- hostname: str,
958
- port: int,
959
- partition_name: str,
960
- resource_name: str,
727
+ url: HttpUrl,
961
728
  ) -> OTCS:
962
729
  """Initialize the OTCS class and parameters and authenticate at OTCS once it is ready.
963
730
 
964
731
  Args:
965
- hostname (str): OTCS hostname
966
- port (int): port number of OTCS
967
- partition_name (str): name of OTDS Partition for Extended ECM users
968
- resource_name (str): name of OTDS resource for Extended ECM
732
+ url (HttpURL):
733
+ The OTCS URL.
734
+
969
735
  Returns:
970
- OTCS: OTCS object
736
+ OTCS:
737
+ The OTCS object
738
+
971
739
  """
972
740
 
973
- logger.info("Connection parameters OTCS (Extended ECM):")
974
- logger.info("OTCS Protocol = %s", self.otcs_settings.protocol)
975
- logger.info(
976
- "OTCS Public Protocol = %s", self.otcs_settings.public_protocol
977
- )
978
- logger.info("OTCS Hostname = %s", hostname)
979
- logger.info("OTCS Public URL = %s", self.otcs_settings.public_url)
980
- logger.info("OTCS Port = %s", str(port))
981
- logger.info("OTCS Admin User = %s", self.otcs_settings.admin)
982
- logger.debug("OTCS Admin Password = %s", self.otcs_settings.password)
983
- logger.info("OTCS User Partition = %s", partition_name)
984
- logger.info("OTCS Resource Name = %s", resource_name)
985
- logger.info(
986
- "OTCS User Default License = %s", self.otcs_settings.license_feature
987
- )
988
- logger.info(
741
+ self.logger.info("Connection parameters OTCS (Extended ECM):")
742
+ self.logger.info("OTCS URL = %s", str(self.settings.otcs.url))
743
+ self.logger.info(
744
+ "OTCS Frontend URL = %s",
745
+ str(self.settings.otcs.url_frontend),
746
+ )
747
+ self.logger.info(
748
+ "OTCS Backend URL = %s",
749
+ str(self.settings.otcs.url_backend),
750
+ )
751
+ self.logger.info("OTCS Admin User = %s", self.settings.otcs.username)
752
+ self.logger.debug(
753
+ "OTCS Admin Password = %s",
754
+ self.settings.otcs.password,
755
+ )
756
+ self.logger.info(
757
+ "OTCS User Partition = %s",
758
+ self.settings.otcs.partition,
759
+ )
760
+ self.logger.info(
761
+ "OTCS Resource Name = %s",
762
+ self.settings.otcs.resource_name,
763
+ )
764
+ self.logger.info(
765
+ "OTCS User Default License = %s",
766
+ self.settings.otcs.license_feature,
767
+ )
768
+ self.logger.info(
989
769
  "OTCS K8s Frontend Pods = %s",
990
- self.otcs_settings.k8s_statefulset_frontend,
770
+ self.settings.k8s.sts_otcs_frontend,
991
771
  )
992
- logger.info(
772
+ self.logger.info(
993
773
  "OTCS K8s Backend Pods = %s",
994
- self.otcs_settings.k8s_statefulset_backend,
774
+ self.settings.k8s.sts_otcs_admin,
995
775
  )
996
- logger.info(
776
+ self.logger.info(
997
777
  "FEME URI = %s",
998
- self.otcs_settings.feme_uri,
778
+ self.settings.otcs.feme_uri,
999
779
  )
1000
780
 
1001
- logger.debug("Checking if OTCS object has already been initialized")
781
+ self.logger.debug("Checking if OTCS object has already been initialized")
1002
782
 
1003
- otds_ticket = (
1004
- self.otds_object.cookie()["OTDSTicket"] if self.otds_object else None
1005
- )
783
+ otds_ticket = self.otds_object.cookie()["OTDSTicket"] if self.otds_object else None
1006
784
  otcs_object = OTCS(
1007
- self.otcs_settings.protocol,
1008
- hostname,
1009
- int(port),
1010
- self.otcs_settings.public_protocol + "://" + self.otcs_settings.public_url,
1011
- self.otcs_settings.admin,
1012
- self.otcs_settings.password,
1013
- partition_name,
1014
- resource_name,
785
+ url.scheme,
786
+ url.host,
787
+ url.port,
788
+ self.settings.otcs.url.scheme + "://" + self.settings.otcs.url.host,
789
+ self.settings.otcs.username,
790
+ self.settings.otcs.password.get_secret_value(),
791
+ self.settings.otcs.partition,
792
+ self.settings.otcs.resource_name,
1015
793
  otds_ticket=otds_ticket,
1016
- base_path=self.otcs_settings.base_path,
1017
- feme_uri=self.otcs_settings.feme_uri,
794
+ base_path=self.settings.otcs.base_path,
795
+ feme_uri=self.settings.otcs.feme_uri,
796
+ logger=self.logger,
1018
797
  )
1019
798
 
1020
799
  # It is important to wait for OTCS to be configured - otherwise we
1021
800
  # may interfere with the OTCS container automation and run into errors
1022
- logger.info("Wait for OTCS to be configured...")
801
+ self.logger.info("Wait for OTCS to be configured...")
1023
802
  otcs_configured = otcs_object.is_configured()
1024
803
  while not otcs_configured:
1025
- logger.warning("OTCS is not configured yet. Waiting 30 seconds...")
804
+ self.logger.warning("OTCS is not configured yet. Waiting 30 seconds...")
1026
805
  time.sleep(30)
1027
806
  otcs_configured = otcs_object.is_configured()
1028
- logger.info("OTCS is configured now.")
807
+ self.logger.info("OTCS is configured now.")
1029
808
 
1030
- logger.info("Authenticating to OTCS...")
809
+ self.logger.info("Authenticating to OTCS...")
1031
810
  otcs_cookie = otcs_object.authenticate()
1032
811
  while otcs_cookie is None:
1033
- logger.warning("Waiting 30 seconds for OTCS to become ready...")
812
+ self.logger.info("Waiting 30 seconds for OTCS to become ready...")
1034
813
  time.sleep(30)
1035
814
  otcs_cookie = otcs_object.authenticate()
1036
- logger.info("OTCS is ready now.")
1037
-
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")
815
+ self.logger.info("OTCS is ready now.")
1042
816
 
1043
817
  if "OTCS_RESSOURCE_ID" not in self.settings.placeholder_values:
1044
- self.settings.placeholder_values["OTCS_RESSOURCE_ID"] = (
1045
- self.otds_object.get_resource(self.otcs_settings.resource_name)[
1046
- "resourceID"
1047
- ]
1048
- )
1049
- logger.debug(
1050
- "Placeholder values after OTCS init = %s",
818
+ self.settings.placeholder_values["OTCS_RESSOURCE_ID"] = self.otds_object.get_resource(
819
+ self.settings.otcs.resource_name,
820
+ )["resourceID"]
821
+ self.logger.debug(
822
+ "Placeholder values after OTCS init -> %s",
1051
823
  self.settings.placeholder_values,
1052
824
  )
1053
825
 
1054
- if self.otawp_settings.enabled:
826
+ if self.settings.otawp.enabled:
1055
827
  otcs_resource = self.otds_object.get_resource(
1056
- self.otcs_settings.resource_name
828
+ self.settings.otcs.resource_name,
1057
829
  )
1058
830
  otcs_resource["logoutURL"] = (
1059
- f"{self.otawp_settings.public_protocol}://{self.otawp_settings.public_url}/home/system/wcp/sso/sso_logout.htm"
831
+ f"{self.settings.otawp.public_protocol}://{self.settings.otawp.public_url}/home/system/wcp/sso/sso_logout.htm"
1060
832
  )
1061
833
  otcs_resource["logoutMethod"] = "GET"
1062
834
 
1063
835
  self.otds_object.update_resource(name="cs", resource=otcs_resource)
1064
836
 
1065
837
  # Allow impersonation of the resource for all users:
1066
- self.otds_object.impersonate_resource(resource_name)
838
+ self.otds_object.impersonate_resource(self.settings.otcs.resource_name)
1067
839
 
1068
840
  return otcs_object
1069
841
 
@@ -1073,49 +845,67 @@ class Customizer:
1073
845
  """Initialize the OTIV (Intelligent Viewing) object and its OTDS settings.
1074
846
 
1075
847
  Args:
848
+ None
849
+
1076
850
  Returns:
1077
- objects: OTIV object
851
+ OTIV:
852
+ The OTIV object.
853
+
1078
854
  """
1079
855
 
1080
- logger.info("Parameters for OTIV (Intelligent Viewing):")
1081
- logger.info("OTDS Resource Name = %s", self.otiv_settings.resource_name)
1082
- logger.info("OTIV License File = %s", self.otiv_settings.license_file)
1083
- logger.info("OTIV Product Name = %s", self.otiv_settings.product_name)
1084
- logger.info(
1085
- "OTIV Product Description = %s", self.otiv_settings.product_description
856
+ self.logger.info("Parameters for OTIV (Intelligent Viewing):")
857
+ self.logger.info(
858
+ "OTDS Resource Name = %s",
859
+ self.settings.otiv.resource_name,
860
+ )
861
+ self.logger.info(
862
+ "OTIV License File = %s",
863
+ self.settings.otiv.license_file,
864
+ )
865
+ self.logger.info(
866
+ "OTIV Product Name = %s",
867
+ self.settings.otiv.product_name,
868
+ )
869
+ self.logger.info(
870
+ "OTIV Product Description = %s",
871
+ self.settings.otiv.product_description,
872
+ )
873
+ self.logger.info(
874
+ "OTIV License Feature = %s",
875
+ self.settings.otiv.license_feature,
1086
876
  )
1087
- logger.info("OTIV License Feature = %s", self.otiv_settings.license_feature)
1088
877
 
1089
878
  otiv_object = OTIV(
1090
- resource_name=self.otiv_settings.resource_name,
1091
- product_name=self.otiv_settings.product_name,
1092
- product_description=self.otiv_settings.product_description,
1093
- license_file=self.otiv_settings.license_file,
1094
- default_license=self.otiv_settings.license_feature,
879
+ resource_name=self.settings.otiv.resource_name,
880
+ product_name=self.settings.otiv.product_name,
881
+ product_description=self.settings.otiv.product_description,
882
+ license_file=self.settings.otiv.license_file,
883
+ default_license=self.settings.otiv.license_feature,
884
+ logger=self.logger,
1095
885
  )
1096
886
 
1097
- otiv_resource = self.otds_object.get_resource(self.otiv_settings.resource_name)
887
+ otiv_resource = self.otds_object.get_resource(self.settings.otiv.resource_name)
1098
888
  while otiv_resource is None:
1099
- logger.warning(
889
+ self.logger.info(
1100
890
  "OTDS Resource -> %s for Intelligent Viewing not found. OTIV may not be ready. Wait 30 sec...",
1101
- self.otiv_settings.resource_name,
891
+ self.settings.otiv.resource_name,
1102
892
  )
1103
893
  time.sleep(30)
1104
894
  otiv_resource = self.otds_object.get_resource(
1105
- self.otiv_settings.resource_name
895
+ self.settings.otiv.resource_name,
1106
896
  )
1107
897
 
1108
898
  otiv_license = self.otds_object.add_license_to_resource(
1109
- self.otiv_settings.license_file,
1110
- self.otiv_settings.product_name,
1111
- self.otiv_settings.product_description,
899
+ self.settings.otiv.license_file,
900
+ self.settings.otiv.product_name,
901
+ self.settings.otiv.product_description,
1112
902
  otiv_resource["resourceID"],
1113
903
  )
1114
904
  if not otiv_license:
1115
- logger.info(
905
+ self.logger.info(
1116
906
  "Couldn't apply license -> %s for product -> %s. Intelligent Viewing may not be deployed!",
1117
- self.otiv_settings.license_file,
1118
- self.otiv_settings.product_name,
907
+ self.settings.otiv.license_file,
908
+ self.settings.otiv.product_name,
1119
909
  )
1120
910
  return None
1121
911
 
@@ -1135,7 +925,7 @@ class Customizer:
1135
925
  )
1136
926
  time.sleep(30)
1137
927
 
1138
- logger.info("OTDS user iv-publisher -> updating oTType=ServiceUser")
928
+ self.logger.info("OTDS user iv-publisher -> updating oTType=ServiceUser")
1139
929
 
1140
930
  return otiv_object
1141
931
 
@@ -1146,103 +936,118 @@ class Customizer:
1146
936
 
1147
937
  Args:
1148
938
  None
939
+
1149
940
  Returns:
1150
- object: OTPD (PowerDocs) object
941
+ OTPD:
942
+ The OTPD (PowerDocs) object.
943
+
1151
944
  """
1152
945
 
1153
- logger.info("Connection parameters OTPD (PowerDocs):")
1154
- logger.info("OTPD Protocol = %s", self.otpd_settings.protocol)
1155
- logger.info("OTPD Hostname = %s", self.otpd_settings.hostname)
1156
- logger.info("OTPD Port = %s", str(self.otpd_settings.port))
1157
- logger.info("OTPD API User = %s", self.otpd_settings.user)
1158
- logger.info("OTPD Tenant = %s", self.otpd_settings.tenant)
1159
- logger.info(
946
+ self.logger.info("Connection parameters OTPD (PowerDocs):")
947
+ self.logger.info(
948
+ "OTPD Protocol = %s",
949
+ self.settings.otpd.url.scheme,
950
+ )
951
+ self.logger.info("OTPD Hostname = %s", self.settings.otpd.url.host)
952
+ self.logger.info("OTPD Port = %s", self.settings.otpd.url.port)
953
+ self.logger.info("OTPD API User = %s", self.settings.otpd.username)
954
+ self.logger.info("OTPD Tenant = %s", self.settings.otpd.tenant)
955
+ self.logger.info(
1160
956
  "OTPD Database Import File = %s",
1161
- (
1162
- self.otpd_settings.db_importfile
1163
- if self.otpd_settings.db_importfile != ""
1164
- else "<not configured>"
1165
- ),
957
+ (self.settings.otpd.db_importfile if self.settings.otpd.db_importfile != "" else "<not configured>"),
1166
958
  )
1167
- logger.info("OTPD K8s Pod Name = %s", self.otpd_settings.k8s_pod_name)
959
+ self.logger.info("OTPD K8s Pod Name = %s", self.settings.k8s.pod_otpd)
1168
960
 
1169
961
  otpd_object = OTPD(
1170
- self.otpd_settings.protocol,
1171
- self.otpd_settings.hostname,
1172
- int(self.otpd_settings.port),
1173
- self.otpd_settings.user,
1174
- self.otpd_settings.password,
962
+ self.settings.otpd.url.scheme,
963
+ self.settings.otpd.url.host,
964
+ self.settings.otpd.url.port,
965
+ self.settings.otpd.username,
966
+ self.settings.otpd.password,
967
+ logger=self.logger,
1175
968
  )
1176
969
 
1177
970
  # wait until the OTPD pod is in ready state
1178
- self.k8s_object.wait_pod_condition(self.otpd_settings.k8s_pod_name, "Ready")
971
+ self.k8s_object.wait_pod_condition(self.settings.k8s.pod_otpd, "Ready")
1179
972
 
1180
973
  # We have a race condition here. Even if the pod is ready
1181
974
  # it may not yet have fully initialized its database.
1182
975
  # Then the "apply_setting()" calls below may fail with
1183
976
  # an error. This should be improved in the future. For now
1184
977
  # we just wait a minute hoping that the DB is initialized then.
1185
- logger.info("Wait some time for PowerDocs database to be initialized...")
1186
- time.sleep(60)
1187
- logger.info("Configure some basic PowerDocs settings...")
978
+ # self.logger.info("Wait some time for PowerDocs database to be initialized...")
979
+ # time.sleep(60)
980
+ # self.logger.info("Configure some basic PowerDocs settings...")
1188
981
 
1189
982
  # Fix settings for local Kubernetes deployments.
1190
983
  # Unclear why this is not the default.
1191
- if otpd_object:
1192
- otpd_object.apply_setting("LocalOtdsUrl", "http://otds/otdsws")
1193
- otpd_object.apply_setting(
1194
- "LocalApplicationServerUrlForContentManager",
1195
- "http://localhost:8080/c4ApplicationServer",
1196
- self.otpd_settings.tenant,
1197
- )
984
+ # if otpd_object:
985
+ # otpd_object.apply_setting("LocalOtdsUrl", "http://otds/otdsws")
986
+ # otpd_object.apply_setting(
987
+ # "LocalApplicationServerUrlForContentManager",
988
+ # "http://localhost:8080/c4ApplicationServer",
989
+ # self.settings.otpd.tenant,
990
+ # )
1198
991
 
1199
992
  return otpd_object
1200
993
 
1201
994
  # end function definition
1202
995
 
1203
- def init_otawp(self):
1204
- """Initialize OTDS for Appworks Platform
1205
- Args:
1206
- Return: None
996
+ def init_otawp(self) -> OTAWP:
997
+ """Initialize OTDS for Appworks Platform.
998
+
999
+ Returns:
1000
+ OTAWP:
1001
+ The AppWorks Platform object.
1002
+
1207
1003
  """
1208
1004
 
1209
- logger.info("Connection parameters OTAWP:")
1210
- logger.info("OTAWP Enabled = %s", str(self.otawp_settings.enabled))
1211
- logger.info("OTAWP Resource = %s", self.otawp_settings.resource_name)
1212
- logger.info("OTAWP Access Role = %s", self.otawp_settings.access_role_name)
1213
- logger.info("OTAWP Admin User = %s", self.otawp_settings.admin)
1214
- logger.debug("OTAWP Password = %s", self.otawp_settings.password)
1215
- logger.info("OTAWP K8s Stateful Set = %s", self.otawp_settings.k8s_statefulset)
1216
- logger.info("OTAWP K8s Config Map = %s", self.otawp_settings.k8s_configmap)
1005
+ self.logger.info("Connection parameters OTAWP:")
1006
+ self.logger.info(
1007
+ "OTAWP Enabled = %s",
1008
+ str(self.settings.otawp.enabled),
1009
+ )
1010
+ self.logger.info(
1011
+ "OTAWP Resource = %s",
1012
+ self.settings.otawp.resource_name,
1013
+ )
1014
+ self.logger.info(
1015
+ "OTAWP Access Role = %s",
1016
+ self.settings.otawp.access_role_name,
1017
+ )
1018
+ self.logger.info("OTAWP Admin User = %s", self.settings.otawp.username)
1019
+ self.logger.debug("OTAWP Password = %s", self.settings.otawp.password)
1020
+ self.logger.info("OTAWP K8s Stateful Set = %s", self.settings.k8s.sts_otawp)
1021
+ self.logger.info("OTAWP K8s Config Map = %s", self.settings.k8s.cm_otawp)
1217
1022
 
1218
- logger.info(
1023
+ self.logger.info(
1219
1024
  "Wait for OTCS to create its OTDS resource with name -> '%s'...",
1220
- self.otcs_settings.resource_name,
1025
+ self.settings.otcs.resource_name,
1221
1026
  )
1222
1027
 
1223
1028
  # Loop to wait for OTCS to create its OTDS resource
1224
1029
  # (we need it to update the AppWorks K8s Config Map):
1225
- otcs_resource = self.otds_object.get_resource(self.otcs_settings.resource_name)
1030
+ otcs_resource = self.otds_object.get_resource(self.settings.otcs.resource_name)
1226
1031
  while otcs_resource is None:
1227
- logger.warning(
1032
+ self.logger.warning(
1228
1033
  "OTDS resource for Content Server with name -> '%s' does not exist yet. Waiting...",
1229
- self.otcs_settings.resource_name,
1034
+ self.settings.otcs.resource_name,
1230
1035
  )
1231
1036
  time.sleep(30)
1232
1037
  otcs_resource = self.otds_object.get_resource(
1233
- self.otcs_settings.resource_name
1038
+ self.settings.otcs.resource_name,
1234
1039
  )
1235
1040
 
1236
1041
  otcs_resource_id = otcs_resource["resourceID"]
1237
1042
 
1238
- logger.info("OTDS resource ID for Content Server -> %s", otcs_resource_id)
1043
+ self.logger.info("Found Content Server OTDS resource ID -> %s", otcs_resource_id)
1239
1044
 
1240
1045
  # make sure code is idempotent and only try to add ressource if it doesn't exist already:
1241
- awp_resource = self.otds_object.get_resource(self.otawp_settings.resource_name)
1046
+ awp_resource = self.otds_object.get_resource(self.settings.otawp.resource_name)
1242
1047
  if not awp_resource:
1243
- logger.info(
1048
+ self.logger.info(
1244
1049
  "OTDS resource -> '%s' for AppWorks Platform does not yet exist. Creating...",
1245
- self.otawp_settings.resource_name,
1050
+ self.settings.otawp.resource_name,
1246
1051
  )
1247
1052
  # Create a Python dict with the special payload we need for AppWorks:
1248
1053
  additional_payload = {}
@@ -1449,170 +1254,177 @@ class Customizer:
1449
1254
  "name": "fBaseURL",
1450
1255
  "value": "http://appworks:8080/home/system/app/otdspush",
1451
1256
  },
1452
- {"name": "fUsername", "value": self.otawp_settings.admin},
1453
- {"name": "fPassword", "value": self.otawp_settings.password},
1257
+ {"name": "fUsername", "value": self.settings.otawp.username},
1258
+ {
1259
+ "name": "fPassword",
1260
+ "value": self.settings.otawp.password.get_secret_value(),
1261
+ },
1454
1262
  ]
1455
1263
 
1456
1264
  awp_resource = self.otds_object.add_resource(
1457
- name=self.otawp_settings.resource_name,
1265
+ name=self.settings.otawp.resource_name,
1458
1266
  description="AppWorks Platform",
1459
1267
  display_name="AppWorks Platform",
1460
1268
  additional_payload=additional_payload,
1461
1269
  )
1462
1270
  else:
1463
- logger.info(
1464
- "OTDS resource -> %s for AppWorks Platform does already exist.",
1465
- self.otawp_settings.resource_name,
1271
+ self.logger.info(
1272
+ "OTDS resource -> '%s' for AppWorks Platform does already exist.",
1273
+ self.settings.otawp.resource_name,
1466
1274
  )
1467
1275
 
1468
1276
  awp_resource_id = awp_resource["resourceID"]
1469
1277
 
1470
- logger.info("OTDS resource ID for AppWorks Platform -> %s", awp_resource_id)
1278
+ self.logger.info(
1279
+ "OTDS resource ID for AppWorks Platform -> %s",
1280
+ awp_resource_id,
1281
+ )
1471
1282
 
1472
1283
  self.settings.placeholder_values["OTAWP_RESOURCE_ID"] = str(awp_resource_id)
1473
1284
 
1474
- logger.debug(
1475
- "Placeholder values after OTAWP init = %s", self.settings.placeholder_values
1285
+ self.logger.debug(
1286
+ "Placeholder values after OTAWP init = %s",
1287
+ self.settings.placeholder_values,
1476
1288
  )
1477
1289
 
1478
- logger.info("Update AppWorks Kubernetes Config Map with OTDS resource IDs...")
1290
+ self.logger.info(
1291
+ "Update AppWorks Kubernetes Config Map with OTDS resource IDs...",
1292
+ )
1479
1293
 
1480
- config_map = self.k8s_object.get_config_map(self.otawp_settings.k8s_configmap)
1294
+ config_map = self.k8s_object.get_config_map(self.settings.k8s.cm_otawp)
1481
1295
  if not config_map:
1482
- logger.error(
1296
+ self.logger.error(
1483
1297
  "Failed to retrieve AppWorks Kubernetes Config Map -> %s",
1484
- self.otawp_settings.k8s_configmap,
1298
+ self.settings.k8s.cm_otawp,
1485
1299
  )
1486
1300
  else:
1487
- solution = yaml.safe_load(config_map.data["solution.yaml"]) # type: ignore
1301
+ solution = yaml.safe_load(config_map.data["solution.yaml"])
1488
1302
 
1489
1303
  # Change values as required
1490
- solution["platform"]["organizations"]["system"]["otds"][
1491
- "resourceId"
1492
- ] = awp_resource_id
1493
- solution["platform"]["content"]["ContentServer"][
1494
- "contentServerUrl"
1495
- ] = f"{self.otcs_settings.public_protocol}://{self.otcs_settings.public_url}/cs/cs"
1496
- solution["platform"]["content"]["ContentServer"][
1497
- "contentServerSupportDirectoryUrl"
1498
- ] = f"{self.otcs_settings.public_protocol}://{self.otcs_settings.public_url}/cssupport"
1499
- solution["platform"]["content"]["ContentServer"][
1500
- "otdsResourceId"
1501
- ] = otcs_resource_id
1304
+ solution["platform"]["organizations"]["system"]["otds"]["resourceId"] = awp_resource_id
1305
+ solution["platform"]["content"]["ContentServer"]["contentServerUrl"] = (
1306
+ f"{self.settings.otcs.url!s}{self.settings.otcs.base_path}"
1307
+ )
1308
+ solution["platform"]["content"]["ContentServer"]["contentServerSupportDirectoryUrl"] = (
1309
+ f"{self.settings.otcs.url!s}/cssupport"
1310
+ )
1311
+ solution["platform"]["content"]["ContentServer"]["otdsResourceId"] = otcs_resource_id
1502
1312
  solution["platform"]["authenticators"]["OTDS_auth"]["publicLoginUrl"] = (
1503
- self.otds_settings.public_protocol
1504
- + "://"
1505
- + self.otds_settings.public_url
1506
- + "/otdsws/login"
1313
+ str(self.settings.otds.url) + "/otdsws/login"
1507
1314
  )
1508
- solution["platform"]["security"]["contentSecurityPolicy"] = (
1509
- "frame-ancestors 'self' "
1510
- + self.otcs_settings.public_protocol
1511
- + "://"
1512
- + self.otcs_settings.public_url
1315
+ solution["platform"]["security"]["contentSecurityPolicy"] = "frame-ancestors 'self' " + str(
1316
+ self.settings.otcs.url,
1513
1317
  )
1514
- data = {"solution.yaml": yaml.dump(solution)}
1318
+ config_map.data["solution.yaml"] = yaml.dump(solution)
1515
1319
  result = self.k8s_object.replace_config_map(
1516
- self.otawp_settings.k8s_configmap, data
1320
+ self.settings.k8s.cm_otawp,
1321
+ config_map.data,
1517
1322
  )
1518
1323
  if result:
1519
- logger.info("Successfully updated AppWorks Solution YAML.")
1324
+ self.logger.info("Successfully updated AppWorks solution YAML.")
1520
1325
  else:
1521
- logger.error("Failed to update AppWorks Solution YAML.")
1522
- logger.debug("Solution YAML for AppWorks -> %s", solution)
1326
+ self.logger.error("Failed to update AppWorks Solution YAML.")
1327
+ self.logger.debug("Solution YAML for AppWorks -> %s", solution)
1523
1328
 
1524
- logger.info("Scale AppWorks Kubernetes Stateful Set to 1...")
1329
+ self.logger.info("Scale AppWorks Kubernetes Stateful Set to 1...")
1525
1330
  self.k8s_object.scale_stateful_set(
1526
- sts_name=self.otawp_settings.k8s_statefulset, scale=1
1331
+ sts_name=self.settings.k8s.sts_otawp,
1332
+ scale=1,
1527
1333
  )
1528
1334
 
1529
1335
  # Add the OTCS Admin user to the AppWorks Access Role in OTDS
1530
1336
  self.otds_object.add_user_to_access_role(
1531
- "Access to " + self.otawp_settings.resource_name, "otadmin@otds.admin"
1337
+ "Access to " + self.settings.otawp.resource_name,
1338
+ "otadmin@otds.admin",
1532
1339
  )
1533
1340
 
1534
1341
  # Loop to wait for OTCS to create its OTDS user partition:
1535
1342
  otcs_partition = self.otds_object.get_partition(
1536
- self.otcs_settings.partition, show_error=False
1343
+ self.settings.otcs.partition,
1344
+ show_error=False,
1537
1345
  )
1538
1346
  while otcs_partition is None:
1539
- logger.warning(
1347
+ self.logger.warning(
1540
1348
  "OTDS user partition for Content Server with name -> '%s' does not exist yet. Waiting...",
1541
- self.otcs_settings.partition,
1349
+ self.settings.otcs.partition,
1542
1350
  )
1543
1351
 
1544
1352
  time.sleep(30)
1545
1353
  otcs_partition = self.otds_object.get_partition(
1546
- self.otcs_settings.partition, show_error=False
1354
+ self.settings.otcs.partition,
1355
+ show_error=False,
1547
1356
  )
1548
1357
 
1549
1358
  # Add the OTDS user partition for OTCS to the AppWorks Platform Access Role in OTDS.
1550
1359
  # This will effectvely sync all OTCS users with AppWorks Platform:
1551
1360
  self.otds_object.add_partition_to_access_role(
1552
- self.otawp_settings.access_role_name, self.otcs_settings.partition
1361
+ self.settings.otawp.access_role_name,
1362
+ self.settings.otcs.partition,
1553
1363
  )
1554
1364
 
1555
1365
  # Add the OTDS admin partition to the AppWorks Platform Access Role in OTDS.
1556
1366
  self.otds_object.add_partition_to_access_role(
1557
- self.otawp_settings.access_role_name, self.otds_settings.admin_partition
1367
+ self.settings.otawp.access_role_name,
1368
+ self.settings.otds.admin_partition,
1558
1369
  )
1559
1370
 
1560
1371
  # Set Group inclusion for Access Role for OTAWP to "True":
1561
1372
  self.otds_object.update_access_role_attributes(
1562
- self.otawp_settings.access_role_name,
1373
+ self.settings.otawp.access_role_name,
1563
1374
  [{"name": "pushAllGroups", "values": ["True"]}],
1564
1375
  )
1565
1376
 
1566
1377
  # Add ResourceID User to OTDSAdmin to allow push
1567
1378
  self.otds_object.add_user_to_group(
1568
- user=str(awp_resource_id) + "@otds.admin", group="otdsadmins@otds.admin"
1379
+ user=str(awp_resource_id) + "@otds.admin",
1380
+ group="otdsadmins@otds.admin",
1569
1381
  )
1570
1382
 
1571
1383
  # Allow impersonation for all users:
1572
- self.otds_object.impersonate_resource(self.otawp_settings.resource_name)
1384
+ self.otds_object.impersonate_resource(self.settings.otawp.resource_name)
1573
1385
 
1574
1386
  # Add SPS license for OTAWP
1575
1387
  # check if the license file exists, otherwise skip for versions pre 24.1
1576
- if os.path.isfile(self.otawp_settings.license_file):
1577
- logger.info(
1388
+ if os.path.isfile(self.settings.otawp.license_file):
1389
+ self.logger.info(
1578
1390
  "Found OTAWP license file -> '%s', assiging it to ressource '%s'...",
1579
- self.otawp_settings.license_file,
1580
- self.otawp_settings.resource_name,
1391
+ self.settings.otawp.license_file,
1392
+ self.settings.otawp.resource_name,
1581
1393
  )
1582
1394
 
1583
1395
  otawp_license = self.otds_object.add_license_to_resource(
1584
- self.otawp_settings.license_file,
1585
- self.otawp_settings.product_name,
1586
- self.otawp_settings.product_description,
1396
+ self.settings.otawp.license_file,
1397
+ self.settings.otawp.product_name,
1398
+ self.settings.otawp.product_description,
1587
1399
  awp_resource["resourceID"],
1588
1400
  )
1589
1401
  if not otawp_license:
1590
- logger.error(
1402
+ self.logger.error(
1591
1403
  "Couldn't apply license -> '%s' for product -> '%s' to OTDS resource -> '%s'",
1592
- self.otawp_settings.license_file,
1593
- self.otawp_settings.product_name,
1404
+ self.settings.otawp.license_file,
1405
+ self.settings.otawp.product_name,
1594
1406
  awp_resource["resourceID"],
1595
1407
  )
1596
1408
  else:
1597
- logger.info(
1409
+ self.logger.info(
1598
1410
  "Successfully applied license -> '%s' for product -> '%s' to OTDS resource -> '%s'",
1599
- self.otawp_settings.license_file,
1600
- self.otawp_settings.product_name,
1411
+ self.settings.otawp.license_file,
1412
+ self.settings.otawp.product_name,
1601
1413
  awp_resource["resourceID"],
1602
1414
  )
1603
1415
 
1604
1416
  # Assign AppWorks license to Content Server Members Partiton and otds.admin:
1605
- for partition_name in ["otds.admin", self.otcs_settings.partition]:
1417
+ for partition_name in ["otds.admin", self.settings.otcs.partition]:
1606
1418
  if self.otds_object.is_partition_licensed(
1607
1419
  partition_name=partition_name,
1608
1420
  resource_id=awp_resource["resourceID"],
1609
1421
  license_feature="USERS",
1610
- license_name=self.otawp_settings.product_name,
1422
+ license_name=self.settings.otawp.product_name,
1611
1423
  ):
1612
- logger.info(
1613
- "Partition -> %s is already licensed for -> %s (%s)",
1424
+ self.logger.info(
1425
+ "Partition -> '%s' is already licensed for -> '%s' (%s)",
1614
1426
  partition_name,
1615
- self.otawp_settings.product_name,
1427
+ self.settings.otawp.product_name,
1616
1428
  "USERS",
1617
1429
  )
1618
1430
  else:
@@ -1620,62 +1432,85 @@ class Customizer:
1620
1432
  partition_name,
1621
1433
  awp_resource["resourceID"],
1622
1434
  "USERS",
1623
- self.otawp_settings.product_name,
1435
+ self.settings.otawp.product_name,
1624
1436
  )
1625
1437
  if not assigned_license:
1626
- logger.error(
1438
+ self.logger.error(
1627
1439
  "Partition -> '%s' could not be assigned to license -> '%s' (%s)",
1628
1440
  partition_name,
1629
- self.otawp_settings.product_name,
1441
+ self.settings.otawp.product_name,
1630
1442
  "USERS",
1631
1443
  )
1632
1444
  else:
1633
- logger.info(
1445
+ self.logger.info(
1634
1446
  "Partition -> '%s' successfully assigned to license -> '%s' (%s)",
1635
1447
  partition_name,
1636
- self.otawp_settings.product_name,
1448
+ self.settings.otawp.product_name,
1637
1449
  "USERS",
1638
1450
  )
1639
1451
  otawp_object = OTAWP(
1640
- self.otawp_settings.protocol,
1641
- self.otawp_settings.k8s_statefulset,
1642
- str(self.otawp_settings.port),
1452
+ self.settings.otawp.protocol,
1453
+ self.settings.k8s.sts_otawp,
1454
+ str(self.settings.otawp.port),
1643
1455
  "sysadmin",
1644
- self.otawp_settings.password,
1456
+ self.settings.otawp.password.get_secret_value(),
1645
1457
  "",
1458
+ self.settings.otcs.partition,
1459
+ self.settings.otds.admin_partition,
1460
+ self.settings.k8s.cm_otawp,
1461
+ otcs_resource_id,
1462
+ self.settings.otds.url,
1463
+ self.settings.otcs.url,
1464
+ self.settings.otcs.base_path,
1465
+ self.settings.otawp.license_file,
1466
+ self.settings.otawp.product_name,
1467
+ self.settings.otawp.product_description,
1468
+ logger=self.logger,
1646
1469
  )
1647
1470
  return otawp_object
1648
1471
 
1649
1472
  # end method definition
1650
1473
 
1651
- def restart_otcs_service(self, otcs_object: OTCS, extra_wait_time: int = 60):
1652
- """Restart the Content Server service in all OTCS pods
1474
+ def restart_otcs_service(
1475
+ self,
1476
+ backend: OTCS,
1477
+ frontend: OTCS,
1478
+ extra_wait_time: int = 60,
1479
+ ) -> None:
1480
+ """Restart the Content Server service in all OTCS pods.
1653
1481
 
1654
1482
  Args:
1655
- otcs_object: OTCS class instance (object)
1483
+ backend:
1484
+ OTCS object of the backend.
1485
+ frontend:
1486
+ OTCS object of the frontend.
1487
+ extra_wait_time (int):
1488
+ Extra wait time after the restart to make sure pods are responsive again.
1489
+
1656
1490
  Returns:
1657
1491
  None
1492
+
1658
1493
  """
1659
1494
 
1660
1495
  if not self.k8s_object:
1661
- logger.warning(
1662
- "Kubernetes integration not available, skipping restart of services"
1496
+ self.logger.warning(
1497
+ "Kubernetes integration not available, skipping restart of services",
1663
1498
  )
1664
1499
  return
1665
1500
 
1666
- logger.info("Restart OTCS frontend and backend pods...")
1501
+ self.logger.info("Restart OTCS frontend and backend pods...")
1667
1502
 
1668
1503
  # Restart all frontends:
1669
- for x in range(0, self.otcs_settings.replicas_frontend):
1670
- pod_name = self.otcs_settings.k8s_statefulset_frontend + "-" + str(x)
1504
+ for x in range(self.settings.k8s.sts_otcs_frontend_replicas):
1505
+ pod_name = self.settings.k8s.sts_otcs_frontend + "-" + str(x)
1671
1506
 
1672
- logger.info("Deactivate Liveness probe for pod -> '%s'", pod_name)
1507
+ self.logger.info("Deactivate Liveness probe for pod -> '%s'", pod_name)
1673
1508
  self.k8s_object.exec_pod_command(
1674
1509
  pod_name,
1675
1510
  ["/bin/sh", "-c", "touch /tmp/keepalive"],
1676
1511
  container="otcs-frontend-container",
1677
1512
  )
1678
- logger.info("Restarting pod -> '%s'", pod_name)
1513
+ self.logger.info("Restarting pod -> '%s'", pod_name)
1679
1514
  self.k8s_object.exec_pod_command(
1680
1515
  pod_name,
1681
1516
  ["/bin/sh", "-c", "/opt/opentext/cs/stop_csserver"],
@@ -1688,16 +1523,16 @@ class Customizer:
1688
1523
  )
1689
1524
 
1690
1525
  # Restart all backends:
1691
- for x in range(0, self.otcs_settings.replicas_backend):
1692
- pod_name = self.otcs_settings.k8s_statefulset_backend + "-" + str(x)
1526
+ for x in range(self.settings.k8s.sts_otcs_admin_replicas):
1527
+ pod_name = self.settings.k8s.sts_otcs_admin + "-" + str(x)
1693
1528
 
1694
- logger.info("Deactivate Liveness probe for pod -> '%s'", pod_name)
1529
+ self.logger.info("Deactivate Liveness probe for pod -> '%s'", pod_name)
1695
1530
  self.k8s_object.exec_pod_command(
1696
1531
  pod_name,
1697
1532
  ["/bin/sh", "-c", "touch /tmp/keepalive"],
1698
1533
  container="otcs-admin-container",
1699
1534
  )
1700
- logger.info("Restarting pod -> '%s'", pod_name)
1535
+ self.logger.info("Restarting pod -> '%s'", pod_name)
1701
1536
  self.k8s_object.exec_pod_command(
1702
1537
  pod_name,
1703
1538
  ["/bin/sh", "-c", "/opt/opentext/cs/stop_csserver"],
@@ -1709,218 +1544,241 @@ class Customizer:
1709
1544
  container="otcs-admin-container",
1710
1545
  )
1711
1546
 
1712
- logger.info("Re-Authenticating to OTCS after restart of pods...")
1713
- otcs_cookie = otcs_object.authenticate(revalidate=True)
1547
+ # Reauthenticate at frontend:
1548
+ self.logger.info(
1549
+ "Re-Authenticating to OTCS frontend after restart of frontend pods...",
1550
+ )
1551
+ otcs_cookie = frontend.authenticate(revalidate=True)
1552
+ while otcs_cookie is None:
1553
+ self.logger.info("Waiting 30 seconds for OTCS frontend to become ready...")
1554
+ time.sleep(30)
1555
+ otcs_cookie = frontend.authenticate(revalidate=True)
1556
+ self.logger.info("OTCS frontend is ready again.")
1557
+
1558
+ # Reauthenticate at backend:
1559
+ self.logger.info(
1560
+ "Re-Authenticating to OTCS backend after restart of backend pods...",
1561
+ )
1562
+ otcs_cookie = backend.authenticate(revalidate=True)
1714
1563
  while otcs_cookie is None:
1715
- logger.warning("Waiting 30 seconds for OTCS to become ready...")
1564
+ self.logger.info("Waiting 30 seconds for OTCS backend to become ready...")
1716
1565
  time.sleep(30)
1717
- otcs_cookie = otcs_object.authenticate(revalidate=True)
1718
- logger.info("OTCS is ready again.")
1566
+ otcs_cookie = backend.authenticate(revalidate=True)
1567
+ self.logger.info("OTCS backend is ready again.")
1719
1568
 
1720
1569
  # Reactivate Liveness probes in all pods:
1721
- for x in range(0, self.otcs_settings.replicas_frontend):
1722
- pod_name = self.otcs_settings.k8s_statefulset_frontend + "-" + str(x)
1570
+ for x in range(self.settings.k8s.sts_otcs_frontend_replicas):
1571
+ pod_name = self.settings.k8s.sts_otcs_frontend + "-" + str(x)
1723
1572
 
1724
- logger.info("Reactivate Liveness probe for pod -> '%s'", pod_name)
1573
+ self.logger.info("Reactivate Liveness probe for pod -> '%s'", pod_name)
1725
1574
  self.k8s_object.exec_pod_command(
1726
1575
  pod_name,
1727
1576
  ["/bin/sh", "-c", "rm /tmp/keepalive"],
1728
1577
  container="otcs-frontend-container",
1729
1578
  )
1730
1579
 
1731
- for x in range(0, self.otcs_settings.replicas_backend):
1732
- pod_name = self.otcs_settings.k8s_statefulset_backend + "-" + str(x)
1580
+ for x in range(self.settings.k8s.sts_otcs_admin_replicas):
1581
+ pod_name = self.settings.k8s.sts_otcs_admin + "-" + str(x)
1733
1582
 
1734
- logger.info("Reactivate Liveness probe for pod -> '%s'", pod_name)
1583
+ self.logger.info("Reactivate Liveness probe for pod -> '%s'", pod_name)
1735
1584
  self.k8s_object.exec_pod_command(
1736
1585
  pod_name,
1737
1586
  ["/bin/sh", "-c", "rm /tmp/keepalive"],
1738
1587
  container="otcs-admin-container",
1739
1588
  )
1740
1589
 
1741
- logger.info("Restart OTCS frontend and backend pods has been completed.")
1590
+ self.logger.info("Restart OTCS frontend and backend pods has been completed.")
1742
1591
 
1743
1592
  # optional, give some additional time to make sure service is responsive
1744
1593
  if extra_wait_time > 0:
1745
- logger.info(
1594
+ self.logger.info(
1746
1595
  "Wait %s seconds to make sure OTCS is responsive again...",
1747
1596
  str(extra_wait_time),
1748
1597
  )
1749
1598
  time.sleep(extra_wait_time)
1750
- logger.info("Continue customizing...")
1599
+ self.logger.info("Continue customizing...")
1751
1600
 
1752
1601
  # end method definition
1753
1602
 
1754
1603
  def restart_otac_service(self) -> bool:
1755
- """Restart the Archive Center spawner service in OTAC pod
1604
+ """Restart the Archive Center spawner service in OTAC pod.
1756
1605
 
1757
- Args:
1758
- None
1759
1606
  Returns:
1760
- bool: True if restart was done, False if error occured
1607
+ bool: True if restart was done, False if error occured.
1608
+
1761
1609
  """
1762
1610
 
1763
- if not self.otac_settings.enabled:
1611
+ if not self.settings.otac.enabled:
1764
1612
  return False
1765
1613
 
1766
- logger.info(
1614
+ self.logger.info(
1767
1615
  "Restarting spawner service in Archive Center pod -> '%s'",
1768
- self.otac_settings.k8s_pod_name,
1616
+ self.settings.k8s.pod_otac,
1769
1617
  )
1770
1618
  # The Archive Center Spawner needs to be run in "interactive" mode - otherwise the command will "hang":
1771
1619
  # The "-c" parameter is not required in this case
1772
1620
  # False is given as parameter as OTAC writes non-errors to stderr
1773
1621
  response = self.k8s_object.exec_pod_command_interactive(
1774
- self.otac_settings.k8s_pod_name,
1775
- ["/bin/sh", "/etc/init.d/spawner restart"],
1776
- 60,
1777
- False,
1622
+ pod_name=self.settings.k8s.pod_otac,
1623
+ commands=["/bin/sh", "/etc/init.d/spawner restart"],
1624
+ timeout=60,
1625
+ write_stderr_to_error_log=False,
1778
1626
  )
1779
1627
 
1780
- if response:
1781
- return True
1782
- else:
1783
- return False
1628
+ return bool(response)
1784
1629
 
1785
1630
  # end method definition
1786
1631
 
1787
- def restart_otawp_pod(self):
1788
- """Delete the AppWorks Platform Pod to make Kubernetes restart it.
1632
+ def restart_otawp_pod(self) -> None:
1633
+ """Delete the AppWorks Platform Pod to make Kubernetes restart it."""
1789
1634
 
1790
- Args:
1791
- Returns:
1792
- None
1793
- """
1794
-
1795
- self.k8s_object.delete_pod(self.otawp_settings.k8s_statefulset + "-0")
1635
+ self.k8s_object.delete_pod(self.settings.k8s.sts_otawp + "-0")
1796
1636
 
1797
1637
  # end method definition
1798
1638
 
1799
- def consolidate_otds(self):
1800
- """Consolidate OTDS resources
1801
- Args:
1802
- Return: None
1803
- """
1639
+ def consolidate_otds(self) -> None:
1640
+ """Consolidate OTDS resources."""
1804
1641
 
1805
- self.otds_object.consolidate(self.otcs_settings.resource_name)
1642
+ self.otds_object.consolidate(self.settings.otcs.resource_name)
1806
1643
 
1807
- if self.otawp_settings.enabled: # is AppWorks Platform deployed?
1808
- self.otds_object.consolidate(self.otawp_settings.resource_name)
1644
+ if self.settings.otawp.enabled: # is AppWorks Platform deployed?
1645
+ self.otds_object.consolidate(self.settings.otawp.resource_name)
1809
1646
 
1810
1647
  # end method definition
1811
1648
 
1812
- def import_powerdocs_configuration(self, otpd_object: OTPD):
1813
- """Import a database export (zip file) into the PowerDocs database
1649
+ def import_powerdocs_configuration(self, otpd_object: OTPD) -> None:
1650
+ """Import a database export (zip file) into the PowerDocs database.
1814
1651
 
1815
1652
  Args:
1816
- otpd_object (object): PowerDocs object
1653
+ otpd_object (OTPD):
1654
+ The PowerDocs object.
1655
+
1817
1656
  """
1818
1657
 
1819
- if self.otpd_settings.db_importfile.startswith("http"):
1658
+ if self.settings.otpd.db_importfile.startswith("http"):
1820
1659
  # Download file from remote location specified by the OTPD_DBIMPORTFILE
1821
1660
  # this must be a public place without authentication:
1822
- logger.info(
1661
+ self.logger.info(
1823
1662
  "Download PowerDocs database file from URL -> '%s'",
1824
- self.otpd_settings.db_importfile,
1663
+ self.settings.otpd.db_importfile,
1825
1664
  )
1826
1665
 
1827
1666
  try:
1828
- package = requests.get(self.otpd_settings.db_importfile, timeout=60)
1667
+ package = requests.get(self.settings.otpd.db_importfile, timeout=60)
1829
1668
  package.raise_for_status()
1830
- logger.info(
1669
+ self.logger.info(
1831
1670
  "Successfully downloaded PowerDocs database file -> '%s'; status code -> %s",
1832
- self.otpd_settings.db_importfile,
1671
+ self.settings.otpd.db_importfile,
1833
1672
  package.status_code,
1834
1673
  )
1835
- filename = "/tmp/otpd_db_import.zip"
1674
+ filename = os.path.join(tempfile.gettempdir(), "otpd_db_import.zip")
1836
1675
  with open(filename, mode="wb") as localfile:
1837
1676
  localfile.write(package.content)
1838
1677
 
1839
- logger.info(
1678
+ self.logger.info(
1840
1679
  "Starting import on %s://%s:%s of %s",
1841
- self.otpd_settings.protocol,
1842
- self.otpd_settings.hostname,
1843
- self.otpd_settings.port,
1844
- self.otpd_settings.db_importfile,
1680
+ self.settings.otpd.url.scheme,
1681
+ self.settings.otpd.url.host,
1682
+ self.settings.otpd.url.port,
1683
+ self.settings.otpd.db_importfile,
1845
1684
  )
1846
- response = otpd_object.import_database(filename=filename)
1847
- logger.info("Response -> %s", response)
1685
+ response = otpd_object.import_database(file_path=filename)
1686
+ self.logger.info("Response -> %s", response)
1848
1687
 
1849
- except requests.exceptions.HTTPError as err:
1850
- logger.error("Request error -> %s", err)
1688
+ except requests.exceptions.HTTPError:
1689
+ self.logger.error("HTTP request error!")
1851
1690
 
1852
1691
  # end method definition
1853
1692
 
1854
- def set_maintenance_mode(self, enable: bool = True):
1855
- """Enable or Disable Maintenance Mode
1693
+ def set_maintenance_mode(self, enable: bool = True) -> None:
1694
+ """Enable or Disable Maintenance Mode.
1695
+
1696
+ This redirects the Kubernetes Ingress to a maintenace web page.
1856
1697
 
1857
1698
  Args:
1858
- enable (bool, optional): _description_. Defaults to True.
1699
+ enable (bool, optional):
1700
+ Whether or not to activate the maintenance mode web page.
1701
+ Defaults to True.
1702
+
1859
1703
  """
1860
- if enable and self.k8s_settings.enabled:
1704
+
1705
+ if enable and self.settings.k8s.enabled:
1861
1706
  self.log_header("Enable Maintenance Mode")
1862
- logger.info(
1863
- "Put OTCS frontends in Maitenance Mode by changing the Kubernetes Ingress backend service..."
1707
+ self.logger.info(
1708
+ "Put OTCS frontends in Maitenance Mode by changing the Kubernetes Ingress backend service...",
1864
1709
  )
1865
1710
  self.k8s_object.update_ingress_backend_services(
1866
- self.otcs_settings.k8s_ingress,
1711
+ self.settings.k8s.ingress_otxecm,
1867
1712
  "otcs",
1868
- self.otcs_settings.maintenance_service_name,
1869
- self.otcs_settings.mainteance_service_port,
1713
+ self.settings.k8s.maintenance_service_name,
1714
+ self.settings.k8s.maintenance_service_port,
1870
1715
  )
1871
- logger.info("OTCS frontend is now in Maintenance Mode!")
1872
- elif not self.k8s_settings.enabled:
1873
- logger.warning(
1874
- "Kubernetes Integration disabled - Cannot Enable/Disable Maintenance Mode"
1716
+ self.logger.info("OTCS frontend is now in Maintenance Mode!")
1717
+ elif not self.settings.k8s.enabled:
1718
+ self.logger.warning(
1719
+ "Kubernetes Integration disabled - Cannot Enable/Disable Maintenance Mode",
1875
1720
  )
1876
1721
  self.k8s_object = None
1877
1722
  else:
1878
1723
  # Changing the Ingress backend service to OTCS frontend service:
1879
- logger.info(
1880
- "Put OTCS frontend back in Production Mode by changing the Kubernetes Ingress backend service..."
1724
+ self.logger.info(
1725
+ "Put OTCS frontend back in Production Mode by changing the Kubernetes Ingress backend service...",
1881
1726
  )
1882
1727
  self.k8s_object.update_ingress_backend_services(
1883
- self.otcs_settings.k8s_ingress,
1728
+ self.settings.k8s.ingress_otxecm,
1884
1729
  "otcs",
1885
- self.otcs_settings.hostname_frontend,
1886
- self.otcs_settings.port_frontend,
1730
+ self.settings.otcs.url_frontend.host,
1731
+ self.settings.otcs.url_frontend.port,
1887
1732
  )
1888
- logger.info("OTCS frontend is now back in Production Mode!")
1733
+ self.logger.info("OTCS frontend is now back in Production Mode!")
1889
1734
 
1890
1735
  # end method definition
1891
1736
 
1892
- def customization_run(self):
1893
- """Central function to initiate the customization"""
1894
- # Set Timer for duration calculation
1895
- self.settings.customizer_start_time = self.settings.customizer_end_time = (
1896
- datetime.now()
1897
- )
1737
+ def init_customizer(self) -> bool:
1738
+ """Initialize all objects used by the customizer.
1739
+
1740
+ This includes:
1741
+ * OTDS
1742
+ * Kubernetes (K8S)
1743
+ * AppWorks Platform
1744
+ * OTCS (frontend + backend)
1745
+ * OTAC (Archive Center)
1746
+ * OTIV (Intelligent Viewing)
1747
+ * OTPD (PowerDocs)
1748
+ * Core Share
1749
+ * Microsoft 365
1750
+ * Aviator Search
1898
1751
 
1899
- # Initialize the OTDS, OTCS and OTPD objects and wait for the
1900
- # pods to be ready. If any of this fails we bail out:
1752
+ Returns:
1753
+ bool:
1754
+ True = success. False = error.
1755
+
1756
+ """
1901
1757
 
1902
1758
  self.log_header("Initialize OTDS")
1903
1759
 
1904
1760
  self.otds_object = self.init_otds()
1905
1761
  if not self.otds_object:
1906
- logger.error("Failed to initialize OTDS - exiting...")
1907
- sys.exit()
1762
+ self.logger.error("Failed to initialize OTDS - exiting...")
1763
+ return False
1908
1764
 
1909
1765
  # Establish in-cluster Kubernetes connection
1910
1766
  self.log_header("Initialize Kubernetes")
1911
- if self.k8s_settings.enabled:
1912
- self.k8s_object = self.init_k8s()
1913
-
1914
- if not self.k8s_object:
1915
- logger.error("Failed to initialize Kubernetes - exiting...")
1916
- sys.exit()
1917
-
1918
- # Put Frontend in Maintenance mode to make sure nobody interferes
1919
- # during customization:
1920
- if self.otcs_settings.maintenance_mode:
1921
- self.set_maintenance_mode(True)
1767
+ if self.settings.k8s.enabled:
1768
+ try:
1769
+ self.k8s_object = self.init_k8s()
1770
+
1771
+ if not self.k8s_object:
1772
+ self.logger.error("Failed to initialize Kubernetes - exiting...")
1773
+ return False
1774
+ except Exception as err:
1775
+ self.logger.error(
1776
+ "Failed to initialize Kubernetes, disabling Kubernetes integration...",
1777
+ )
1778
+ self.logger.debug(err)
1779
+ self.settings.k8s.enabled = False
1922
1780
 
1923
- if self.otawp_settings.enabled: # is AppWorks Platform deployed?
1781
+ if self.settings.otawp.enabled: # is AppWorks Platform deployed?
1924
1782
  self.log_header("Initialize OTAWP")
1925
1783
 
1926
1784
  # Configure required OTDS resources as AppWorks doesn't do this on its own:
@@ -1930,119 +1788,142 @@ class Customizer:
1930
1788
 
1931
1789
  self.log_header("Initialize OTCS backend")
1932
1790
  self.otcs_backend_object = self.init_otcs(
1933
- self.otcs_settings.hostname_backend,
1934
- int(self.otcs_settings.port_backend),
1935
- self.otcs_settings.partition,
1936
- self.otcs_settings.resource_name,
1791
+ url=self.settings.otcs.url_backend,
1937
1792
  )
1938
1793
  if not self.otcs_backend_object:
1939
- logger.error("Failed to initialize OTCS backend - exiting...")
1794
+ self.logger.error("Failed to initialize OTCS backend - exiting...")
1940
1795
  sys.exit()
1941
1796
 
1942
1797
  self.log_header("Initialize OTCS frontend")
1943
1798
  self.otcs_frontend_object = self.init_otcs(
1944
- self.otcs_settings.hostname_frontend,
1945
- int(self.otcs_settings.port_frontend),
1946
- self.otcs_settings.partition,
1947
- self.otcs_settings.resource_name,
1799
+ url=self.settings.otcs.url_frontend,
1948
1800
  )
1949
1801
  if not self.otcs_frontend_object:
1950
- logger.error("Failed to initialize OTCS frontend - exiting...")
1951
- sys.exit()
1802
+ self.logger.error("Failed to initialize OTCS frontend - exiting...")
1803
+ return False
1952
1804
 
1953
- if self.otac_settings.enabled: # is Archive Center deployed?
1805
+ if self.settings.otac.enabled: # is Archive Center deployed?
1954
1806
  self.log_header("Initialize OTAC")
1955
1807
 
1956
1808
  self.otac_object = self.init_otac()
1957
1809
  if not self.otac_object:
1958
- logger.error("Failed to initialize OTAC - exiting...")
1959
- sys.exit()
1810
+ self.logger.error("Failed to initialize OTAC - exiting...")
1811
+ return False
1960
1812
  else:
1961
1813
  self.otac_object = None
1962
1814
 
1963
- if self.otiv_settings.enabled: # is Intelligent Viewing deployed?
1815
+ if self.settings.otiv.enabled: # is Intelligent Viewing deployed?
1964
1816
  self.log_header("Initialize OTIV")
1965
1817
 
1966
1818
  self.otiv_object = self.init_otiv()
1967
1819
  else:
1968
1820
  self.otiv_object = None
1969
1821
 
1970
- if self.otpd_settings.enabled: # is PowerDocs deployed?
1822
+ if self.settings.otpd.enabled: # is PowerDocs deployed?
1971
1823
  self.log_header("Initialize OTPD")
1972
1824
 
1973
1825
  self.otpd_object = self.init_otpd()
1974
1826
  if not self.otpd_object:
1975
- logger.error("Failed to initialize OTPD - exiting...")
1976
- sys.exit()
1827
+ self.logger.error("Failed to initialize OTPD - exiting...")
1828
+ return False
1977
1829
  else:
1978
1830
  self.otpd_object = None
1979
1831
 
1980
- if self.core_share_settings.enabled: # is Core Share enabled?
1832
+ if self.settings.coreshare.enabled: # is Core Share enabled?
1981
1833
  self.log_header("Initialize Core Share")
1982
1834
 
1983
1835
  self.core_share_object = self.init_coreshare()
1984
1836
  if not self.core_share_object:
1985
- logger.error("Failed to initialize Core Share - exiting...")
1986
- sys.exit()
1837
+ self.logger.error("Failed to initialize Core Share - exiting...")
1838
+ return False
1987
1839
  else:
1988
1840
  self.core_share_object = None
1989
1841
 
1990
1842
  if (
1991
- self.m365_settings.enabled
1992
- and self.m365_settings.user != ""
1993
- and self.m365_settings.password != ""
1843
+ self.settings.m365.enabled and self.settings.m365.username != "" and self.settings.m365.password != ""
1994
1844
  ): # is M365 enabled?
1995
1845
  self.log_header("Initialize Microsoft 365")
1996
1846
 
1997
1847
  # Initialize the M365 object and connection to M365 Graph API:
1998
1848
  self.m365_object = self.init_m365()
1999
1849
  if not self.m365_object:
2000
- logger.error("Failed to initialize Microsoft 365!")
2001
- sys.exit()
1850
+ self.logger.error("Failed to initialize Microsoft 365!")
1851
+ return False
2002
1852
 
2003
- if self.avts_settings.enabled:
1853
+ if self.settings.avts.enabled:
2004
1854
  self.log_header("Initialize Aviator Search")
2005
1855
  self.avts_object = self.init_avts()
2006
1856
  if not self.avts_object:
2007
- logger.error("Failed to initialize Aviator Search")
2008
- sys.exit()
1857
+ self.logger.error("Failed to initialize Aviator Search")
1858
+ return False
2009
1859
  else:
2010
1860
  self.avts_object = None
2011
1861
 
2012
- self.log_header("Processing Payload")
1862
+ return True
1863
+
1864
+ # end method definition
1865
+
1866
+ def customization_run(self) -> bool:
1867
+ """Central method to initiate the customization."""
1868
+
1869
+ success = True
1870
+
1871
+ # Set Timer for duration calculation
1872
+ self.customizer_start_time = datetime.now(timezone.utc)
1873
+
1874
+ if not self.init_customizer():
1875
+ self.logger.error("Initialization of customizer failed!")
1876
+ return False
1877
+
1878
+ # Put Frontend in Maintenance mode to make sure nobody interferes
1879
+ # during customization:
1880
+ if self.settings.otcs.maintenance_mode:
1881
+ self.set_maintenance_mode(enable=True)
1882
+
1883
+ self.log_header("Collect payload files to process")
2013
1884
 
2014
1885
  cust_payload_list = []
2015
1886
  # Is uncompressed payload provided?
2016
- if os.path.exists(self.settings.cust_payload):
2017
- logger.info("Found payload file -> '%s'", self.settings.cust_payload)
1887
+ if self.settings.cust_payload and os.path.exists(self.settings.cust_payload):
1888
+ self.logger.info("Found payload file -> '%s'", self.settings.cust_payload)
2018
1889
  cust_payload_list.append(self.settings.cust_payload)
2019
1890
  # Is compressed payload provided?
2020
- if os.path.exists(self.settings.cust_payload_gz):
2021
- logger.info(
2022
- "Found compressed payload file -> '%s'", self.settings.cust_payload_gz
1891
+ if self.settings.cust_payload_gz and os.path.exists(
1892
+ self.settings.cust_payload_gz,
1893
+ ):
1894
+ self.logger.info(
1895
+ "Found compressed payload file -> '%s'",
1896
+ self.settings.cust_payload_gz,
2023
1897
  )
2024
1898
  cust_payload_list.append(self.settings.cust_payload_gz)
2025
1899
 
2026
1900
  # do we have additional payload as an external file?
2027
- if os.path.exists(self.settings.cust_payload_external):
1901
+ if self.settings.cust_payload_external and os.path.exists(
1902
+ self.settings.cust_payload_external,
1903
+ ):
2028
1904
  for filename in sorted(
2029
- os.scandir(self.settings.cust_payload_external), key=lambda e: e.name
1905
+ os.scandir(self.settings.cust_payload_external),
1906
+ key=lambda e: e.name,
2030
1907
  ):
2031
1908
  if filename.is_file() and os.path.getsize(filename) > 0:
2032
- logger.info("Found external payload file -> '%s'", filename.path)
1909
+ self.logger.info(
1910
+ "Found external payload file -> '%s'",
1911
+ filename.path,
1912
+ )
2033
1913
  cust_payload_list.append(filename.path)
2034
- else:
2035
- logger.info(
2036
- "No external payload file -> '%s'", self.settings.cust_payload_external
1914
+ elif self.settings.cust_payload_external:
1915
+ self.logger.warning(
1916
+ "External payload file -> '%s' does not exist!",
1917
+ self.settings.cust_payload_external,
2037
1918
  )
2038
1919
 
2039
1920
  for cust_payload in cust_payload_list:
2040
- # Open the payload file. If this fails we bail out:
2041
- logger.info("Starting processing of payload -> '%s'", cust_payload)
1921
+ self.log_header("Start processing of payload -> '{}'".format(cust_payload))
2042
1922
 
2043
1923
  # Set startTime for duration calculation
2044
- start_time = datetime.now()
1924
+ start_time = datetime.now(timezone.utc)
2045
1925
 
1926
+ # Create payload object:
2046
1927
  payload_object = Payload(
2047
1928
  payload_source=cust_payload,
2048
1929
  custom_settings_dir=self.settings.cust_settings_dir,
@@ -2053,22 +1934,26 @@ class Customizer:
2053
1934
  otcs_frontend_object=self.otcs_frontend_object,
2054
1935
  otcs_restart_callback=self.restart_otcs_service,
2055
1936
  otiv_object=self.otiv_object,
1937
+ otpd_object=self.otpd_object,
2056
1938
  m365_object=self.m365_object,
2057
1939
  core_share_object=self.core_share_object,
2058
1940
  browser_automation_object=self.browser_automation_object,
2059
1941
  placeholder_values=self.settings.placeholder_values, # this dict includes placeholder replacements for the Ressource IDs of OTAWP and OTCS
2060
1942
  log_header_callback=self.log_header,
2061
1943
  stop_on_error=self.settings.stop_on_error,
2062
- aviator_enabled=self.aviator_settings.enabled,
2063
- upload_status_files=self.otcs_settings.upload_status_files,
1944
+ aviator_enabled=self.settings.aviator.enabled,
1945
+ upload_status_files=self.settings.otcs.upload_status_files,
2064
1946
  otawp_object=self.otawp_object,
2065
1947
  avts_object=self.avts_object,
1948
+ logger=self.logger,
2066
1949
  )
2067
1950
  # Load the payload file and initialize the payload sections:
2068
1951
  if not payload_object.init_payload():
2069
- logger.error(
2070
- "Failed to initialize payload -> %s - skipping...", cust_payload
1952
+ self.logger.error(
1953
+ "Failed to initialize payload -> '%s' - skipping payload file...",
1954
+ cust_payload,
2071
1955
  )
1956
+ success = False
2072
1957
  continue
2073
1958
 
2074
1959
  # Now process the payload in the defined ordering:
@@ -2078,119 +1963,134 @@ class Customizer:
2078
1963
  self.consolidate_otds()
2079
1964
 
2080
1965
  # Upload payload file for later review to Enterprise Workspace
2081
- if self.otcs_settings.upload_config_files:
1966
+ if self.settings.otcs.upload_config_files:
2082
1967
  self.log_header("Upload Payload file to Extended ECM")
2083
1968
  response = self.otcs_backend_object.get_node_from_nickname(
2084
- self.settings.cust_target_folder_nickname
1969
+ nickname=self.settings.cust_target_folder_nickname,
2085
1970
  )
2086
1971
  target_folder_id = self.otcs_backend_object.get_result_value(
2087
- response, "id"
1972
+ response=response,
1973
+ key="id",
2088
1974
  )
2089
1975
  if not target_folder_id:
2090
1976
  target_folder_id = 2000 # use Enterprise Workspace as fallback
2091
1977
  # Write YAML file with upadated payload (including IDs, etc.).
2092
- # We need to write to /tmp as initial location is read-only:
1978
+ # We need to write to a temporary location as initial location is read-only:
2093
1979
  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
1980
+ payload_file = payload_file.removesuffix(".gz.b64")
1981
+ payload_file = payload_file.replace(".tfvars", ".yaml").replace(
1982
+ ".tf",
1983
+ ".yaml",
2098
1984
  )
2099
- cust_payload = "/tmp/" + payload_file
1985
+ cust_payload = os.path.join(tempfile.gettempdir(), payload_file)
2100
1986
 
2101
1987
  with open(cust_payload, "w", encoding="utf-8") as file:
2102
- yaml.dump(payload_object.get_payload(), file)
1988
+ yaml.dump(
1989
+ data=payload_object.get_payload(
1990
+ drop_bulk_datasources_data=True,
1991
+ ),
1992
+ stream=file,
1993
+ )
2103
1994
 
2104
1995
  # Check if the payload file has been uploaded before.
2105
1996
  # This can happen if we re-run the python container.
2106
1997
  # In this case we add a version to the existing document:
2107
1998
  response = self.otcs_backend_object.get_node_by_parent_and_name(
2108
- int(target_folder_id), os.path.basename(cust_payload)
1999
+ parent_id=int(target_folder_id),
2000
+ name=os.path.basename(cust_payload),
2109
2001
  )
2110
2002
  target_document_id = self.otcs_backend_object.get_result_value(
2111
- response, "id"
2003
+ response=response,
2004
+ key="id",
2112
2005
  )
2113
2006
  if target_document_id:
2114
2007
  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",
2008
+ node_id=int(target_document_id),
2009
+ file_url=cust_payload,
2010
+ file_name=os.path.basename(cust_payload),
2011
+ mime_type="text/plain",
2012
+ description="Updated payload file after re-run of customization",
2120
2013
  )
2121
2014
  else:
2122
2015
  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),
2016
+ file_url=cust_payload,
2017
+ file_name=os.path.basename(cust_payload),
2018
+ mime_type="text/plain",
2019
+ parent_id=int(target_folder_id),
2127
2020
  )
2128
2021
 
2129
- duration = datetime.now() - start_time
2022
+ duration = datetime.now(timezone.utc) - start_time
2130
2023
  self.log_header(
2131
2024
  "Customizer completed processing of payload -> {} in {}".format(
2132
2025
  cust_payload,
2133
2026
  duration,
2134
- )
2027
+ ),
2135
2028
  )
2029
+ # end for cust_payload in cust_payload_list
2136
2030
 
2137
- if self.otcs_settings.maintenance_mode:
2138
- self.set_maintenance_mode(False)
2031
+ if self.settings.otcs.maintenance_mode:
2032
+ self.set_maintenance_mode(enable=False)
2139
2033
 
2140
2034
  # Restart AppWorksPlatform pod if it is deployed (to make settings effective):
2141
- if self.otawp_settings.enabled: # is AppWorks Platform deployed?
2035
+ if self.settings.otawp.enabled: # is AppWorks Platform deployed?
2142
2036
  otawp_resource = self.otds_object.get_resource(
2143
- self.otawp_settings.resource_name
2037
+ name=self.settings.otawp.resource_name,
2144
2038
  )
2145
- if (
2146
- not "allowImpersonation" in otawp_resource
2147
- or not otawp_resource["allowImpersonation"]
2148
- ):
2039
+ if "allowImpersonation" not in otawp_resource or not otawp_resource["allowImpersonation"]:
2149
2040
  # Allow impersonation for all users:
2150
- logger.warning(
2151
- "OTAWP impersonation is not correct in OTDS before OTAWP pod restart!"
2041
+ self.logger.warning(
2042
+ "OTAWP impersonation is not correct in OTDS before OTAWP pod restart!",
2152
2043
  )
2153
2044
  else:
2154
- logger.info(
2155
- "OTAWP impersonation is correct in OTDS before OTAWP pod restart!"
2045
+ self.logger.info(
2046
+ "OTAWP impersonation is correct in OTDS before OTAWP pod restart!",
2156
2047
  )
2157
- logger.info("Restart OTAWP pod...")
2048
+ self.logger.info("Restart OTAWP pod...")
2158
2049
  self.restart_otawp_pod()
2159
- # For some reason we need to double-check that the impersonation for OTAWP has been set correctly
2160
- # and if not set it again:
2050
+ # For some reason we need to double-check that the impersonation
2051
+ # for OTAWP has been set correctly and if not set it again:
2161
2052
  otawp_resource = self.otds_object.get_resource(
2162
- self.otawp_settings.resource_name
2053
+ name=self.settings.otawp.resource_name,
2163
2054
  )
2164
- if (
2165
- not "allowImpersonation" in otawp_resource
2166
- or not otawp_resource["allowImpersonation"]
2167
- ):
2055
+ if "allowImpersonation" not in otawp_resource or not otawp_resource["allowImpersonation"]:
2168
2056
  # Allow impersonation for all users:
2169
- logger.warning(
2170
- "OTAWP impersonation is not correct in OTDS - set it once more..."
2057
+ self.logger.warning(
2058
+ "OTAWP impersonation is not correct in OTDS - set it once more...",
2059
+ )
2060
+ self.otds_object.impersonate_resource(
2061
+ resource_name=self.settings.otawp.resource_name,
2171
2062
  )
2172
- self.otds_object.impersonate_resource(self.otawp_settings.resource_name)
2173
2063
 
2174
- # Upload log file for later review to "Deployment" folder in "Administration" folder
2175
- if (
2176
- os.path.exists(self.settings.cust_log_file)
2177
- and self.otcs_settings.upload_log_file
2178
- ):
2064
+ # Restart Aviator Search (Omnigroup) to ensure group synchronisation is working
2065
+ if self.settings.avts.enabled: # is Aviator Search deployed?
2066
+ self.logger.info(
2067
+ "Restarting Aviator Search Omnigroup server after creation of OTDS ClientID/ClientSecret...",
2068
+ )
2069
+ self.k8s_object.restart_stateful_set(sts_name="idol-omnigroupserver")
2070
+
2071
+ # Upload log file for later review to "Deployment" folder
2072
+ # in "Administration" folder in OTCS Enterprise volume:
2073
+ if os.path.exists(self.settings.cust_log_file) and self.settings.otcs.upload_log_file:
2179
2074
  self.log_header("Upload log file to Extended ECM")
2180
2075
  response = self.otcs_backend_object.get_node_from_nickname(
2181
- self.settings.cust_target_folder_nickname
2076
+ nickname=self.settings.cust_target_folder_nickname,
2077
+ )
2078
+ target_folder_id = self.otcs_backend_object.get_result_value(
2079
+ response=response,
2080
+ key="id",
2182
2081
  )
2183
- target_folder_id = self.otcs_backend_object.get_result_value(response, "id")
2184
2082
  if not target_folder_id:
2185
2083
  target_folder_id = 2000 # use Enterprise Workspace as fallback
2186
2084
  # Check if the log file has been uploaded before.
2187
2085
  # This can happen if we re-run the python container:
2188
2086
  # In this case we add a version to the existing document:
2189
2087
  response = self.otcs_backend_object.get_node_by_parent_and_name(
2190
- int(target_folder_id), os.path.basename(self.settings.cust_log_file)
2088
+ parent_id=int(target_folder_id),
2089
+ name=os.path.basename(self.settings.cust_log_file),
2191
2090
  )
2192
2091
  target_document_id = self.otcs_backend_object.get_result_value(
2193
- response, "id"
2092
+ response=response,
2093
+ key="id",
2194
2094
  )
2195
2095
  if target_document_id:
2196
2096
  response = self.otcs_backend_object.add_document_version(
@@ -2209,37 +2109,14 @@ class Customizer:
2209
2109
  description="Initial Python Log after first run of customization",
2210
2110
  )
2211
2111
 
2212
- self.settings.customizer_end_time = datetime.now()
2112
+ self.customizer_end_time = datetime.now(timezone.utc)
2213
2113
  self.log_header(
2214
2114
  "Customizer completed in {}".format(
2215
- self.settings.customizer_end_time - self.settings.customizer_start_time
2216
- )
2115
+ self.customizer_end_time - self.customizer_start_time,
2116
+ ),
2217
2117
  )
2218
2118
 
2119
+ # Return the success status:
2120
+ return success
2219
2121
 
2220
- if __name__ == "__main__":
2221
- logging.basicConfig(
2222
- format="%(asctime)s %(levelname)s [%(name)s] %(message)s",
2223
- datefmt="%d-%b-%Y %H:%M:%S",
2224
- level=logging.INFO,
2225
- handlers=[
2226
- logging.StreamHandler(sys.stdout),
2227
- ],
2228
- )
2229
-
2230
- my_customizer = Customizer(
2231
- otcs=CustomizerSettingsOTCS(
2232
- hostname="otcs.local.xecm.cloud",
2233
- hostname_backend="otcs-admin-0",
2234
- hostname_frontend="otcs-frontend",
2235
- protocol="http",
2236
- port_backend=8080,
2237
- ),
2238
- otds=CustomizerSettingsOTDS(hostname="otds"),
2239
- otpd=CustomizerSettingsOTPD(enabled=False),
2240
- otac=CustomizerSettingsOTAC(enabled=False),
2241
- k8s=CustomizerSettingsK8S(enabled=True),
2242
- otiv=CustomizerSettingsOTIV(enabled=False),
2243
- )
2244
-
2245
- my_customizer.customization_run()
2122
+ # end method definition