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.
- pyxecm/__init__.py +6 -4
- pyxecm/avts.py +673 -246
- pyxecm/coreshare.py +686 -467
- pyxecm/customizer/__init__.py +16 -4
- pyxecm/customizer/__main__.py +58 -0
- pyxecm/customizer/api/__init__.py +5 -0
- pyxecm/customizer/api/__main__.py +6 -0
- pyxecm/customizer/api/app.py +914 -0
- pyxecm/customizer/api/auth.py +154 -0
- pyxecm/customizer/api/metrics.py +92 -0
- pyxecm/customizer/api/models.py +13 -0
- pyxecm/customizer/api/payload_list.py +865 -0
- pyxecm/customizer/api/settings.py +103 -0
- pyxecm/customizer/browser_automation.py +332 -139
- pyxecm/customizer/customizer.py +1007 -1130
- pyxecm/customizer/exceptions.py +35 -0
- pyxecm/customizer/guidewire.py +322 -0
- pyxecm/customizer/k8s.py +713 -378
- pyxecm/customizer/log.py +107 -0
- pyxecm/customizer/m365.py +2867 -909
- pyxecm/customizer/nhc.py +1169 -0
- pyxecm/customizer/openapi.py +258 -0
- pyxecm/customizer/payload.py +16817 -7467
- pyxecm/customizer/pht.py +699 -285
- pyxecm/customizer/salesforce.py +516 -342
- pyxecm/customizer/sap.py +58 -41
- pyxecm/customizer/servicenow.py +593 -371
- pyxecm/customizer/settings.py +442 -0
- pyxecm/customizer/successfactors.py +408 -346
- pyxecm/customizer/translate.py +83 -48
- pyxecm/helper/__init__.py +5 -2
- pyxecm/helper/assoc.py +83 -43
- pyxecm/helper/data.py +2406 -870
- pyxecm/helper/logadapter.py +27 -0
- pyxecm/helper/web.py +229 -101
- pyxecm/helper/xml.py +527 -171
- pyxecm/maintenance_page/__init__.py +5 -0
- pyxecm/maintenance_page/__main__.py +6 -0
- pyxecm/maintenance_page/app.py +51 -0
- pyxecm/maintenance_page/settings.py +28 -0
- pyxecm/maintenance_page/static/favicon.avif +0 -0
- pyxecm/maintenance_page/templates/maintenance.html +165 -0
- pyxecm/otac.py +234 -140
- pyxecm/otawp.py +1436 -557
- pyxecm/otcs.py +7716 -3161
- pyxecm/otds.py +2150 -919
- pyxecm/otiv.py +36 -21
- pyxecm/otmm.py +1272 -325
- pyxecm/otpd.py +231 -127
- pyxecm-2.0.0.dist-info/METADATA +145 -0
- pyxecm-2.0.0.dist-info/RECORD +54 -0
- {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/WHEEL +1 -1
- pyxecm-1.6.dist-info/METADATA +0 -53
- pyxecm-1.6.dist-info/RECORD +0 -32
- {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info/licenses}/LICENSE +0 -0
- {pyxecm-1.6.dist-info → pyxecm-2.0.0.dist-info}/top_level.txt +0 -0
pyxecm/customizer/customizer.py
CHANGED
|
@@ -1,47 +1,7 @@
|
|
|
1
|
-
"""
|
|
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
|
|
54
|
-
from
|
|
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
|
|
67
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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:
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
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
|
-
|
|
335
|
-
self.core_share_settings = core_share
|
|
56
|
+
"""
|
|
336
57
|
|
|
337
|
-
|
|
338
|
-
self.aviator_settings = aviator
|
|
58
|
+
self.logger = logger
|
|
339
59
|
|
|
340
|
-
#
|
|
341
|
-
self.
|
|
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
|
|
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 =
|
|
360
|
-
"""
|
|
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):
|
|
364
|
-
|
|
365
|
-
|
|
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,
|
|
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",
|
|
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
|
-
|
|
396
|
-
|
|
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",
|
|
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
|
|
136
|
+
self.logger.debug(
|
|
137
|
+
"Microsoft 365 Client ID = %s",
|
|
138
|
+
self.settings.m365.client_id,
|
|
407
139
|
)
|
|
408
|
-
logger.
|
|
409
|
-
"Microsoft 365
|
|
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",
|
|
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",
|
|
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.
|
|
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.
|
|
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.
|
|
441
|
-
client_id=self.
|
|
442
|
-
client_secret=self.
|
|
443
|
-
domain=self.
|
|
444
|
-
sku_id=self.
|
|
445
|
-
teams_app_name=self.
|
|
446
|
-
teams_app_external_id=self.
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
self.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
"
|
|
466
|
-
"
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
self.
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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=
|
|
565
|
-
update_existing_app=
|
|
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
|
|
575
|
-
self.
|
|
576
|
-
self.
|
|
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
|
|
584
|
-
self.
|
|
585
|
-
self.
|
|
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.
|
|
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:
|
|
650
|
-
|
|
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",
|
|
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",
|
|
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",
|
|
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.
|
|
413
|
+
self.settings.avts.client_secret,
|
|
665
414
|
)
|
|
666
|
-
logger.info(
|
|
667
|
-
"Aviator Search User ID = %s",
|
|
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.
|
|
421
|
+
self.settings.avts.password,
|
|
672
422
|
)
|
|
673
423
|
|
|
674
|
-
|
|
675
|
-
otds_url=self.
|
|
676
|
-
base_url=self.
|
|
677
|
-
client_id=self.
|
|
678
|
-
client_secret=self.
|
|
679
|
-
username=self.
|
|
680
|
-
password=self.
|
|
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
|
-
|
|
694
|
-
|
|
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",
|
|
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",
|
|
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",
|
|
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.
|
|
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.
|
|
729
|
-
sso_url=self.
|
|
730
|
-
client_id=self.
|
|
731
|
-
client_secret=self.
|
|
732
|
-
username=self.
|
|
733
|
-
password=self.
|
|
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
|
|
763
|
-
logger.info(
|
|
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.
|
|
515
|
+
self.settings.k8s.kubeconfig_file,
|
|
767
516
|
)
|
|
768
517
|
|
|
769
518
|
k8s_object = K8s(
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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.
|
|
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.
|
|
535
|
+
self.settings.k8s.sts_otcs_frontend,
|
|
787
536
|
)
|
|
788
537
|
sys.exit()
|
|
789
538
|
|
|
790
|
-
self.
|
|
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.
|
|
794
|
-
self.
|
|
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.
|
|
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.
|
|
553
|
+
self.settings.k8s.sts_otcs_admin,
|
|
805
554
|
)
|
|
806
555
|
sys.exit()
|
|
807
556
|
|
|
808
|
-
self.
|
|
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.
|
|
812
|
-
self.
|
|
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
|
-
|
|
575
|
+
OTDS:
|
|
576
|
+
The OTDS object
|
|
577
|
+
|
|
826
578
|
"""
|
|
827
579
|
|
|
828
|
-
logger.info("Connection parameters OTDS:")
|
|
829
|
-
logger.info("OTDS Protocol = %s", self.
|
|
830
|
-
logger.info(
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
logger.info(
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
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.
|
|
841
|
-
hostname=self.
|
|
842
|
-
port=self.
|
|
843
|
-
username=self.
|
|
844
|
-
password=self.
|
|
845
|
-
otds_ticket=self.
|
|
846
|
-
|
|
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.
|
|
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.
|
|
622
|
+
if self.settings.otds.enable_audit:
|
|
860
623
|
otds_object.enable_audit()
|
|
861
624
|
|
|
862
|
-
if self.
|
|
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
|
-
|
|
880
|
-
|
|
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
|
-
|
|
884
|
-
|
|
647
|
+
|
|
648
|
+
Returns:
|
|
649
|
+
The OTAC object.
|
|
650
|
+
|
|
885
651
|
"""
|
|
886
652
|
|
|
887
|
-
logger.info("Connection parameters OTAC:")
|
|
888
|
-
logger.info("OTAC
|
|
889
|
-
logger.info("OTAC
|
|
890
|
-
logger.info("OTAC
|
|
891
|
-
logger.
|
|
892
|
-
logger.info(
|
|
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.
|
|
905
|
-
self.
|
|
906
|
-
int(self.
|
|
907
|
-
self.
|
|
908
|
-
self.
|
|
909
|
-
self.
|
|
910
|
-
self.
|
|
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",
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
resource_name (str): name of OTDS resource for Extended ECM
|
|
732
|
+
url (HttpURL):
|
|
733
|
+
The OTCS URL.
|
|
734
|
+
|
|
969
735
|
Returns:
|
|
970
|
-
OTCS:
|
|
736
|
+
OTCS:
|
|
737
|
+
The OTCS object
|
|
738
|
+
|
|
971
739
|
"""
|
|
972
740
|
|
|
973
|
-
logger.info("Connection parameters OTCS (Extended ECM):")
|
|
974
|
-
logger.info("OTCS
|
|
975
|
-
logger.info(
|
|
976
|
-
"OTCS
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
logger.info(
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
logger.info("OTCS User
|
|
984
|
-
logger.
|
|
985
|
-
|
|
986
|
-
|
|
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.
|
|
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.
|
|
774
|
+
self.settings.k8s.sts_otcs_admin,
|
|
995
775
|
)
|
|
996
|
-
logger.info(
|
|
776
|
+
self.logger.info(
|
|
997
777
|
"FEME URI = %s",
|
|
998
|
-
self.
|
|
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
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
self.
|
|
1011
|
-
self.
|
|
1012
|
-
self.
|
|
1013
|
-
|
|
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.
|
|
1017
|
-
feme_uri=self.
|
|
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.
|
|
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.
|
|
1046
|
-
|
|
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.
|
|
826
|
+
if self.settings.otawp.enabled:
|
|
1055
827
|
otcs_resource = self.otds_object.get_resource(
|
|
1056
|
-
self.
|
|
828
|
+
self.settings.otcs.resource_name,
|
|
1057
829
|
)
|
|
1058
830
|
otcs_resource["logoutURL"] = (
|
|
1059
|
-
f"{self.
|
|
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
|
-
|
|
851
|
+
OTIV:
|
|
852
|
+
The OTIV object.
|
|
853
|
+
|
|
1078
854
|
"""
|
|
1079
855
|
|
|
1080
|
-
logger.info("Parameters for OTIV (Intelligent Viewing):")
|
|
1081
|
-
logger.info(
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
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.
|
|
1091
|
-
product_name=self.
|
|
1092
|
-
product_description=self.
|
|
1093
|
-
license_file=self.
|
|
1094
|
-
default_license=self.
|
|
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.
|
|
887
|
+
otiv_resource = self.otds_object.get_resource(self.settings.otiv.resource_name)
|
|
1098
888
|
while otiv_resource is None:
|
|
1099
|
-
logger.
|
|
889
|
+
self.logger.info(
|
|
1100
890
|
"OTDS Resource -> %s for Intelligent Viewing not found. OTIV may not be ready. Wait 30 sec...",
|
|
1101
|
-
self.
|
|
891
|
+
self.settings.otiv.resource_name,
|
|
1102
892
|
)
|
|
1103
893
|
time.sleep(30)
|
|
1104
894
|
otiv_resource = self.otds_object.get_resource(
|
|
1105
|
-
self.
|
|
895
|
+
self.settings.otiv.resource_name,
|
|
1106
896
|
)
|
|
1107
897
|
|
|
1108
898
|
otiv_license = self.otds_object.add_license_to_resource(
|
|
1109
|
-
self.
|
|
1110
|
-
self.
|
|
1111
|
-
self.
|
|
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.
|
|
1118
|
-
self.
|
|
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
|
-
|
|
941
|
+
OTPD:
|
|
942
|
+
The OTPD (PowerDocs) object.
|
|
943
|
+
|
|
1151
944
|
"""
|
|
1152
945
|
|
|
1153
|
-
logger.info("Connection parameters OTPD (PowerDocs):")
|
|
1154
|
-
logger.info(
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
logger.info("OTPD
|
|
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.
|
|
959
|
+
self.logger.info("OTPD K8s Pod Name = %s", self.settings.k8s.pod_otpd)
|
|
1168
960
|
|
|
1169
961
|
otpd_object = OTPD(
|
|
1170
|
-
self.
|
|
1171
|
-
self.
|
|
1172
|
-
|
|
1173
|
-
self.
|
|
1174
|
-
self.
|
|
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.
|
|
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
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
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
|
-
|
|
1206
|
-
|
|
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(
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
logger.
|
|
1215
|
-
|
|
1216
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
1034
|
+
self.settings.otcs.resource_name,
|
|
1230
1035
|
)
|
|
1231
1036
|
time.sleep(30)
|
|
1232
1037
|
otcs_resource = self.otds_object.get_resource(
|
|
1233
|
-
self.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
1453
|
-
{
|
|
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.
|
|
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.
|
|
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(
|
|
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",
|
|
1285
|
+
self.logger.debug(
|
|
1286
|
+
"Placeholder values after OTAWP init = %s",
|
|
1287
|
+
self.settings.placeholder_values,
|
|
1476
1288
|
)
|
|
1477
1289
|
|
|
1478
|
-
logger.info(
|
|
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.
|
|
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.
|
|
1298
|
+
self.settings.k8s.cm_otawp,
|
|
1485
1299
|
)
|
|
1486
1300
|
else:
|
|
1487
|
-
solution = yaml.safe_load(config_map.data["solution.yaml"])
|
|
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
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
1318
|
+
config_map.data["solution.yaml"] = yaml.dump(solution)
|
|
1515
1319
|
result = self.k8s_object.replace_config_map(
|
|
1516
|
-
self.
|
|
1320
|
+
self.settings.k8s.cm_otawp,
|
|
1321
|
+
config_map.data,
|
|
1517
1322
|
)
|
|
1518
1323
|
if result:
|
|
1519
|
-
logger.info("Successfully updated AppWorks
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
|
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.
|
|
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.
|
|
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.
|
|
1580
|
-
self.
|
|
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.
|
|
1585
|
-
self.
|
|
1586
|
-
self.
|
|
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.
|
|
1593
|
-
self.
|
|
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.
|
|
1600
|
-
self.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1448
|
+
self.settings.otawp.product_name,
|
|
1637
1449
|
"USERS",
|
|
1638
1450
|
)
|
|
1639
1451
|
otawp_object = OTAWP(
|
|
1640
|
-
self.
|
|
1641
|
-
self.
|
|
1642
|
-
str(self.
|
|
1452
|
+
self.settings.otawp.protocol,
|
|
1453
|
+
self.settings.k8s.sts_otawp,
|
|
1454
|
+
str(self.settings.otawp.port),
|
|
1643
1455
|
"sysadmin",
|
|
1644
|
-
self.
|
|
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(
|
|
1652
|
-
|
|
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
|
-
|
|
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(
|
|
1670
|
-
pod_name = self.
|
|
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(
|
|
1692
|
-
pod_name = self.
|
|
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
|
-
|
|
1713
|
-
|
|
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.
|
|
1564
|
+
self.logger.info("Waiting 30 seconds for OTCS backend to become ready...")
|
|
1716
1565
|
time.sleep(30)
|
|
1717
|
-
otcs_cookie =
|
|
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(
|
|
1722
|
-
pod_name = self.
|
|
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(
|
|
1732
|
-
pod_name = self.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
1642
|
+
self.otds_object.consolidate(self.settings.otcs.resource_name)
|
|
1806
1643
|
|
|
1807
|
-
if self.
|
|
1808
|
-
self.otds_object.consolidate(self.
|
|
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 (
|
|
1653
|
+
otpd_object (OTPD):
|
|
1654
|
+
The PowerDocs object.
|
|
1655
|
+
|
|
1817
1656
|
"""
|
|
1818
1657
|
|
|
1819
|
-
if self.
|
|
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.
|
|
1663
|
+
self.settings.otpd.db_importfile,
|
|
1825
1664
|
)
|
|
1826
1665
|
|
|
1827
1666
|
try:
|
|
1828
|
-
package = requests.get(self.
|
|
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.
|
|
1671
|
+
self.settings.otpd.db_importfile,
|
|
1833
1672
|
package.status_code,
|
|
1834
1673
|
)
|
|
1835
|
-
filename = "
|
|
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.
|
|
1842
|
-
self.
|
|
1843
|
-
self.
|
|
1844
|
-
self.
|
|
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(
|
|
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
|
|
1850
|
-
logger.error("
|
|
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):
|
|
1699
|
+
enable (bool, optional):
|
|
1700
|
+
Whether or not to activate the maintenance mode web page.
|
|
1701
|
+
Defaults to True.
|
|
1702
|
+
|
|
1859
1703
|
"""
|
|
1860
|
-
|
|
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.
|
|
1711
|
+
self.settings.k8s.ingress_otxecm,
|
|
1867
1712
|
"otcs",
|
|
1868
|
-
self.
|
|
1869
|
-
self.
|
|
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.
|
|
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.
|
|
1728
|
+
self.settings.k8s.ingress_otxecm,
|
|
1884
1729
|
"otcs",
|
|
1885
|
-
self.
|
|
1886
|
-
self.
|
|
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
|
|
1893
|
-
"""
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
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
|
-
|
|
1900
|
-
|
|
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
|
-
|
|
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.
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
1802
|
+
self.logger.error("Failed to initialize OTCS frontend - exiting...")
|
|
1803
|
+
return False
|
|
1952
1804
|
|
|
1953
|
-
if self.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
1850
|
+
self.logger.error("Failed to initialize Microsoft 365!")
|
|
1851
|
+
return False
|
|
2002
1852
|
|
|
2003
|
-
if self.
|
|
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
|
-
|
|
1857
|
+
self.logger.error("Failed to initialize Aviator Search")
|
|
1858
|
+
return False
|
|
2009
1859
|
else:
|
|
2010
1860
|
self.avts_object = None
|
|
2011
1861
|
|
|
2012
|
-
|
|
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(
|
|
2021
|
-
|
|
2022
|
-
|
|
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(
|
|
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),
|
|
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(
|
|
1909
|
+
self.logger.info(
|
|
1910
|
+
"Found external payload file -> '%s'",
|
|
1911
|
+
filename.path,
|
|
1912
|
+
)
|
|
2033
1913
|
cust_payload_list.append(filename.path)
|
|
2034
|
-
|
|
2035
|
-
logger.
|
|
2036
|
-
"
|
|
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
|
-
|
|
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.
|
|
2063
|
-
upload_status_files=self.
|
|
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...",
|
|
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.
|
|
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,
|
|
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
|
|
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
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
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 =
|
|
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(
|
|
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),
|
|
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,
|
|
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.
|
|
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.
|
|
2035
|
+
if self.settings.otawp.enabled: # is AppWorks Platform deployed?
|
|
2142
2036
|
otawp_resource = self.otds_object.get_resource(
|
|
2143
|
-
self.
|
|
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
|
|
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.
|
|
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
|
-
#
|
|
2175
|
-
if
|
|
2176
|
-
|
|
2177
|
-
|
|
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),
|
|
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,
|
|
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.
|
|
2112
|
+
self.customizer_end_time = datetime.now(timezone.utc)
|
|
2213
2113
|
self.log_header(
|
|
2214
2114
|
"Customizer completed in {}".format(
|
|
2215
|
-
self.
|
|
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
|
-
|
|
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
|