mas-cli 13.25.1__py3-none-any.whl → 13.27.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 mas-cli might be problematic. Click here for more details.
- mas/cli/__init__.py +1 -1
- mas/cli/aiservice/install/__init__.py +11 -0
- mas/cli/aiservice/install/app.py +810 -0
- mas/cli/aiservice/install/argBuilder.py +232 -0
- mas/cli/aiservice/install/argParser.py +742 -0
- mas/cli/aiservice/install/params.py +120 -0
- mas/cli/aiservice/install/summarizer.py +193 -0
- mas/cli/cli.py +16 -3
- mas/cli/gencfg.py +23 -0
- mas/cli/install/app.py +35 -25
- mas/cli/install/argBuilder.py +13 -13
- mas/cli/install/argParser.py +26 -167
- mas/cli/install/catalogs.py +3 -2
- mas/cli/install/params.py +4 -4
- mas/cli/install/settings/db2Settings.py +4 -2
- mas/cli/install/settings/manageSettings.py +3 -27
- mas/cli/install/summarizer.py +8 -27
- mas/cli/templates/facilities-configs.yml.j2 +25 -0
- mas/cli/templates/ibm-mas-tekton.yaml +4407 -547
- mas/cli/update/app.py +8 -5
- mas/cli/validators.py +13 -0
- {mas_cli-13.25.1.data → mas_cli-13.27.0.data}/scripts/mas-cli +4 -0
- {mas_cli-13.25.1.dist-info → mas_cli-13.27.0.dist-info}/METADATA +1 -1
- {mas_cli-13.25.1.dist-info → mas_cli-13.27.0.dist-info}/RECORD +26 -19
- {mas_cli-13.25.1.dist-info → mas_cli-13.27.0.dist-info}/WHEEL +0 -0
- {mas_cli-13.25.1.dist-info → mas_cli-13.27.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,810 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# *****************************************************************************
|
|
3
|
+
# Copyright (c) 2024, 2025 IBM Corporation and other Contributors.
|
|
4
|
+
#
|
|
5
|
+
# All rights reserved. This program and the accompanying materials
|
|
6
|
+
# are made available under the terms of the Eclipse Public License v1.0
|
|
7
|
+
# which accompanies this distribution, and is available at
|
|
8
|
+
# http://www.eclipse.org/legal/epl-v10.html
|
|
9
|
+
#
|
|
10
|
+
# *****************************************************************************
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from sys import exit
|
|
14
|
+
from os import path, getenv
|
|
15
|
+
import re
|
|
16
|
+
import calendar
|
|
17
|
+
|
|
18
|
+
from openshift.dynamic.exceptions import NotFoundError
|
|
19
|
+
|
|
20
|
+
from prompt_toolkit import prompt, print_formatted_text, HTML
|
|
21
|
+
from prompt_toolkit.completion import WordCompleter
|
|
22
|
+
|
|
23
|
+
from tabulate import tabulate
|
|
24
|
+
|
|
25
|
+
from halo import Halo
|
|
26
|
+
|
|
27
|
+
from ...cli import BaseApp
|
|
28
|
+
from .argBuilder import aiServiceInstallArgBuilderMixin
|
|
29
|
+
from .argParser import aiServiceinstallArgParser
|
|
30
|
+
from .summarizer import aiServiceInstallSummarizerMixin
|
|
31
|
+
from .params import requiredParams, optionalParams
|
|
32
|
+
|
|
33
|
+
from ...install.catalogs import supportedCatalogs
|
|
34
|
+
|
|
35
|
+
# AiService relies on SLS, which in turn depends on MongoDB.
|
|
36
|
+
# SLS will utilize the shared MongoDB resource that would be used by MAS if it were deployed within the same OpenShift cluster.
|
|
37
|
+
# AiService utilizes two distinct databases: DB2 is employed by the AiBroker component, while MariaDB supports OpenDataHub (ODH).
|
|
38
|
+
# By default, AiService will deploy DB2 within the same namespace as MAS (db2u), but it will be configured as a separate DB2 instance.
|
|
39
|
+
|
|
40
|
+
from ...install.settings.mongodbSettings import MongoDbSettingsMixin
|
|
41
|
+
from ...install.settings.db2Settings import Db2SettingsMixin
|
|
42
|
+
from ...install.settings.additionalConfigs import AdditionalConfigsMixin
|
|
43
|
+
|
|
44
|
+
from mas.cli.validators import (
|
|
45
|
+
InstanceIDFormatValidator,
|
|
46
|
+
StorageClassValidator
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
from mas.devops.ocp import createNamespace, getStorageClasses
|
|
50
|
+
from mas.devops.mas import (
|
|
51
|
+
getCurrentCatalog,
|
|
52
|
+
getDefaultStorageClasses
|
|
53
|
+
)
|
|
54
|
+
from mas.devops.sls import findSLSByNamespace
|
|
55
|
+
from mas.devops.data import getCatalog
|
|
56
|
+
from mas.devops.tekton import (
|
|
57
|
+
installOpenShiftPipelines,
|
|
58
|
+
updateTektonDefinitions,
|
|
59
|
+
preparePipelinesNamespace,
|
|
60
|
+
prepareInstallSecrets,
|
|
61
|
+
testCLI,
|
|
62
|
+
launchInstallPipelineForAiservice
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
logger = logging.getLogger(__name__)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def logMethodCall(func):
|
|
69
|
+
def wrapper(self, *args, **kwargs):
|
|
70
|
+
logger.debug(f">>> InstallApp.{func.__name__}")
|
|
71
|
+
result = func(self, *args, **kwargs)
|
|
72
|
+
logger.debug(f"<<< InstallApp.{func.__name__}")
|
|
73
|
+
return result
|
|
74
|
+
return wrapper
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class AiServiceInstallApp(BaseApp, aiServiceInstallArgBuilderMixin, aiServiceInstallSummarizerMixin, MongoDbSettingsMixin, Db2SettingsMixin, AdditionalConfigsMixin):
|
|
78
|
+
@logMethodCall
|
|
79
|
+
def processCatalogChoice(self) -> list:
|
|
80
|
+
self.catalogDigest = self.chosenCatalog["catalog_digest"]
|
|
81
|
+
self.catalogMongoDbVersion = self.chosenCatalog["mongo_extras_version_default"]
|
|
82
|
+
applications = {
|
|
83
|
+
"Aibroker": "mas_aibroker_version",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
self.catalogReleases = {}
|
|
87
|
+
self.catalogTable = []
|
|
88
|
+
|
|
89
|
+
# Dynamically fetch the channels from the chosen catalog
|
|
90
|
+
# based on mas core
|
|
91
|
+
for channel in self.chosenCatalog["mas_core_version"]:
|
|
92
|
+
# {"9.1-feature": "9.1.x-feature"}
|
|
93
|
+
self.catalogReleases.update({channel.replace('.x', ''): channel})
|
|
94
|
+
|
|
95
|
+
# Generate catalogTable
|
|
96
|
+
for application, key in applications.items():
|
|
97
|
+
# Add 9.1-feature channel based off 9.0 to those apps that have not onboarded yet
|
|
98
|
+
tempChosenCatalog = self.chosenCatalog[key].copy()
|
|
99
|
+
if '9.1.x-feature' not in tempChosenCatalog:
|
|
100
|
+
tempChosenCatalog.update({"9.1.x-feature": tempChosenCatalog["9.0.x"]})
|
|
101
|
+
|
|
102
|
+
self.catalogTable.append({"": application} | {key.replace(".x", ""): value for key, value in sorted(tempChosenCatalog.items(), reverse=True)})
|
|
103
|
+
|
|
104
|
+
if self.architecture == "s390x":
|
|
105
|
+
summary = [
|
|
106
|
+
"",
|
|
107
|
+
"<u>Catalog Details</u>",
|
|
108
|
+
f"Catalog Image: icr.io/cpopen/ibm-maximo-operator-catalog:{self.getParam('mas_catalog_version')}",
|
|
109
|
+
f"Catalog Digest: {self.catalogDigest}",
|
|
110
|
+
f"MAS Releases: {', '.join(sorted(self.catalogReleases, reverse=True))}",
|
|
111
|
+
f"MongoDb: {self.catalogMongoDbVersion}",
|
|
112
|
+
]
|
|
113
|
+
else:
|
|
114
|
+
summary = [
|
|
115
|
+
"",
|
|
116
|
+
"<u>Catalog Details</u>",
|
|
117
|
+
f"Catalog Image: icr.io/cpopen/ibm-maximo-operator-catalog:{self.getParam('mas_catalog_version')}",
|
|
118
|
+
f"Catalog Digest: {self.catalogDigest}",
|
|
119
|
+
f"MAS Releases: {', '.join(sorted(self.catalogReleases, reverse=True))}",
|
|
120
|
+
f"MongoDb: {self.catalogMongoDbVersion}",
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
return summary
|
|
124
|
+
|
|
125
|
+
@logMethodCall
|
|
126
|
+
def configAibroker(self):
|
|
127
|
+
self.printH1("Configure Aibroker Instance")
|
|
128
|
+
self.printDescription([
|
|
129
|
+
"Instance ID restrictions:",
|
|
130
|
+
" - Must be 3-12 characters long",
|
|
131
|
+
" - Must only use lowercase letters, numbers, and hypen (-) symbol",
|
|
132
|
+
" - Must start with a lowercase letter",
|
|
133
|
+
" - Must end with a lowercase letter or a number"
|
|
134
|
+
])
|
|
135
|
+
self.promptForString("Instance ID", "aibroker_instance_id", validator=InstanceIDFormatValidator())
|
|
136
|
+
|
|
137
|
+
if self.slsMode == 2 and not self.getParam("sls_namespace"):
|
|
138
|
+
self.setParam("sls_namespace", f"mas-{self.getParam('aibroker_instance_id')}-sls")
|
|
139
|
+
|
|
140
|
+
self.configOperationMode()
|
|
141
|
+
|
|
142
|
+
@logMethodCall
|
|
143
|
+
def interactiveMode(self, simplified: bool, advanced: bool) -> None:
|
|
144
|
+
# Interactive mode
|
|
145
|
+
self.interactiveMode = True
|
|
146
|
+
|
|
147
|
+
self.storageClassProvider = "custom"
|
|
148
|
+
self.installAssist = False
|
|
149
|
+
self.installIoT = False
|
|
150
|
+
self.installMonitor = False
|
|
151
|
+
self.installManage = False
|
|
152
|
+
self.installPredict = False
|
|
153
|
+
self.installInspection = False
|
|
154
|
+
self.installOptimizer = False
|
|
155
|
+
self.installFacilities = False
|
|
156
|
+
self.installAiBroker = True
|
|
157
|
+
self.deployCP4D = False
|
|
158
|
+
self.db2SetAffinity = False
|
|
159
|
+
self.db2SetTolerations = False
|
|
160
|
+
self.slsLicenseFileLocal = None
|
|
161
|
+
|
|
162
|
+
if simplified:
|
|
163
|
+
self.showAdvancedOptions = False
|
|
164
|
+
elif advanced:
|
|
165
|
+
self.showAdvancedOptions = True
|
|
166
|
+
else:
|
|
167
|
+
self.chooseInstallFlavour()
|
|
168
|
+
|
|
169
|
+
# Catalog
|
|
170
|
+
self.configCatalog()
|
|
171
|
+
if not self.devMode:
|
|
172
|
+
self.validateCatalogSource()
|
|
173
|
+
self.licensePrompt()
|
|
174
|
+
|
|
175
|
+
# Storage Classes
|
|
176
|
+
self.configStorageClasses()
|
|
177
|
+
|
|
178
|
+
# Licensing (SLS and DRO)
|
|
179
|
+
self.configSLS()
|
|
180
|
+
self.configDRO()
|
|
181
|
+
self.configICRCredentials()
|
|
182
|
+
|
|
183
|
+
self.configCertManager()
|
|
184
|
+
self.configAibroker()
|
|
185
|
+
if self.devMode:
|
|
186
|
+
self.configAppChannel("aibroker")
|
|
187
|
+
|
|
188
|
+
self.aibrokerSettings()
|
|
189
|
+
|
|
190
|
+
# Dependencies
|
|
191
|
+
self.configMongoDb()
|
|
192
|
+
self.setDB2DefaultSettings()
|
|
193
|
+
|
|
194
|
+
@logMethodCall
|
|
195
|
+
def nonInteractiveMode(self) -> None:
|
|
196
|
+
self.interactiveMode = False
|
|
197
|
+
|
|
198
|
+
# Set defaults
|
|
199
|
+
# ---------------------------------------------------------------------
|
|
200
|
+
# Unless a config file named "mongodb-system.yaml" is provided via the additional configs mechanism we will be installing a new MongoDb instance
|
|
201
|
+
self.setParam("mongodb_action", "install")
|
|
202
|
+
|
|
203
|
+
self.storageClassProvider = "custom"
|
|
204
|
+
self.installAssist = False
|
|
205
|
+
self.installIoT = False
|
|
206
|
+
self.installMonitor = False
|
|
207
|
+
self.installManage = False
|
|
208
|
+
self.installPredict = False
|
|
209
|
+
self.installInspection = False
|
|
210
|
+
self.installFacilities = False
|
|
211
|
+
self.installOptimizer = False
|
|
212
|
+
self.installAiBroker = True
|
|
213
|
+
self.deployCP4D = False
|
|
214
|
+
self.db2SetAffinity = False
|
|
215
|
+
self.db2SetTolerations = False
|
|
216
|
+
self.slsLicenseFileLocal = None
|
|
217
|
+
|
|
218
|
+
self.approvals = {
|
|
219
|
+
"approval_aibroker": {"id": "app-cfg-aibroker"}, # After Aibroker workspace has been configured
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
self.setDB2DefaultSettings()
|
|
223
|
+
|
|
224
|
+
for key, value in vars(self.args).items():
|
|
225
|
+
# These fields we just pass straight through to the parameters and fail if they are not set
|
|
226
|
+
if key in requiredParams:
|
|
227
|
+
if value is None:
|
|
228
|
+
self.fatalError(f"{key} must be set")
|
|
229
|
+
self.setParam(key, value)
|
|
230
|
+
|
|
231
|
+
# These fields we just pass straight through to the parameters
|
|
232
|
+
elif key in optionalParams:
|
|
233
|
+
if value is not None:
|
|
234
|
+
self.setParam(key, value)
|
|
235
|
+
|
|
236
|
+
elif key == "non_prod":
|
|
237
|
+
if not value:
|
|
238
|
+
self.operationalMode = 1
|
|
239
|
+
self.setParam("environment_type", "production")
|
|
240
|
+
else:
|
|
241
|
+
self.operationalMode = 2
|
|
242
|
+
self.setParam("mas_annotations", "mas.ibm.com/operationalMode=nonproduction")
|
|
243
|
+
self.setParam("environment_type", "non-production")
|
|
244
|
+
|
|
245
|
+
elif key == "additional_configs":
|
|
246
|
+
self.localConfigDir = value
|
|
247
|
+
# If there is a file named mongodb-system.yaml we will use this as a BYO MongoDB datasource
|
|
248
|
+
if self.localConfigDir is not None and path.exists(path.join(self.localConfigDir, "mongodb-system.yaml")):
|
|
249
|
+
self.setParam("mongodb_action", "byo")
|
|
250
|
+
self.setParam("sls_mongodb_cfg_file", "/workspace/additional-configs/mongodb-system.yaml")
|
|
251
|
+
|
|
252
|
+
elif key == "pod_templates":
|
|
253
|
+
# For the named configurations we will convert into the path
|
|
254
|
+
if value in ["best-effort", "guaranteed"]:
|
|
255
|
+
self.setParam("mas_pod_templates_dir", path.join(self.templatesDir, "pod-templates", value))
|
|
256
|
+
else:
|
|
257
|
+
self.setParam("mas_pod_templates_dir", value)
|
|
258
|
+
|
|
259
|
+
# We check for both None and "" values for the application channel parameters
|
|
260
|
+
# value = None means the parameter wasn't set at all
|
|
261
|
+
# value = "" means the paramerter was explicitly set to "don't install this application"
|
|
262
|
+
elif key == "aibroker_channel":
|
|
263
|
+
if value is not None and value != "":
|
|
264
|
+
self.setParam("mas_app_channel_aibroker", value)
|
|
265
|
+
self.installAiBroker = True
|
|
266
|
+
|
|
267
|
+
# Manage advanced settings that need extra processing
|
|
268
|
+
elif key == "mas_app_settings_server_bundle_size":
|
|
269
|
+
if value is not None:
|
|
270
|
+
self.setParam(key, value)
|
|
271
|
+
if value in ["jms", "snojms"]:
|
|
272
|
+
self.setParam("mas_app_settings_persistent_volumes_flag", "true")
|
|
273
|
+
|
|
274
|
+
# MongoDB
|
|
275
|
+
elif key == "mongodb_namespace":
|
|
276
|
+
if value is not None and value != "":
|
|
277
|
+
self.setParam(key, value)
|
|
278
|
+
self.setParam("sls_mongodb_cfg_file", f"/workspace/configs/mongo-{value}.yml")
|
|
279
|
+
|
|
280
|
+
# SLS
|
|
281
|
+
elif key == "license_file":
|
|
282
|
+
if value is not None and value != "":
|
|
283
|
+
self.slsLicenseFileLocal = value
|
|
284
|
+
self.setParam("sls_action", "install")
|
|
285
|
+
elif key == "dedicated_sls":
|
|
286
|
+
if value:
|
|
287
|
+
self.setParam("sls_namespace", f"mas-{self.args.aibroker_instance_id}-sls")
|
|
288
|
+
|
|
289
|
+
# These settings are used by the CLI rather than passed to the PipelineRun
|
|
290
|
+
elif key == "storage_accessmode":
|
|
291
|
+
if value is None:
|
|
292
|
+
self.fatalError(f"{key} must be set")
|
|
293
|
+
self.pipelineStorageAccessMode = value
|
|
294
|
+
elif key == "storage_pipeline":
|
|
295
|
+
if value is None:
|
|
296
|
+
self.fatalError(f"{key} must be set")
|
|
297
|
+
self.pipelineStorageClass = value
|
|
298
|
+
|
|
299
|
+
elif key.startswith("approval_"):
|
|
300
|
+
if key not in self.approvals:
|
|
301
|
+
raise KeyError(f"{key} is not a supported approval workflow ID: {self.approvals.keys()}")
|
|
302
|
+
|
|
303
|
+
if value != "":
|
|
304
|
+
valueParts = value.split(":")
|
|
305
|
+
if len(valueParts) != 3:
|
|
306
|
+
self.fatalError(f"Unsupported format for {key} ({value}). Expected MAX_RETRIES:RETRY_DELAY:IGNORE_FAILURE")
|
|
307
|
+
else:
|
|
308
|
+
try:
|
|
309
|
+
self.approvals[key]["maxRetries"] = int(valueParts[0])
|
|
310
|
+
self.approvals[key]["retryDelay"] = int(valueParts[1])
|
|
311
|
+
self.approvals[key]["ignoreFailure"] = bool(valueParts[2])
|
|
312
|
+
except ValueError:
|
|
313
|
+
self.fatalError(f"Unsupported format for {key} ({value}). Expected int:int:boolean")
|
|
314
|
+
|
|
315
|
+
# Arguments that we don't need to do anything with
|
|
316
|
+
elif key in ["accept_license", "dev_mode", "skip_pre_check", "skip_grafana_install", "no_confirm", "no_wait_for_pvc", "help", "advanced", "simplified"]:
|
|
317
|
+
pass
|
|
318
|
+
|
|
319
|
+
elif key == "manual_certificates":
|
|
320
|
+
if value is not None:
|
|
321
|
+
self.setParam("mas_manual_cert_mgmt", True)
|
|
322
|
+
self.manualCertsDir = value
|
|
323
|
+
else:
|
|
324
|
+
self.setParam("mas_manual_cert_mgmt", False)
|
|
325
|
+
self.manualCertsDir = None
|
|
326
|
+
|
|
327
|
+
elif key == "enable_ipv6":
|
|
328
|
+
self.setParam("enable_ipv6", True)
|
|
329
|
+
|
|
330
|
+
# Fail if there's any arguments we don't know how to handle
|
|
331
|
+
else:
|
|
332
|
+
print(f"Unknown option: {key} {value}")
|
|
333
|
+
self.fatalError(f"Unknown option: {key} {value}")
|
|
334
|
+
|
|
335
|
+
# Load the catalog information
|
|
336
|
+
self.chosenCatalog = getCatalog(self.getParam("mas_catalog_version"))
|
|
337
|
+
|
|
338
|
+
# License file is only optional for existing SLS instance
|
|
339
|
+
if self.slsLicenseFileLocal is None:
|
|
340
|
+
if self.getParam("install_sls_aiservice") != "false":
|
|
341
|
+
self.fatalError("--license-file must be set for new SLS install")
|
|
342
|
+
|
|
343
|
+
# Once we've processed the inputs, we should validate the catalog source & prompt to accept the license terms
|
|
344
|
+
if not self.devMode:
|
|
345
|
+
self.validateCatalogSource()
|
|
346
|
+
self.licensePrompt()
|
|
347
|
+
|
|
348
|
+
@logMethodCall
|
|
349
|
+
def install(self, argv):
|
|
350
|
+
"""
|
|
351
|
+
Install Aiservice
|
|
352
|
+
"""
|
|
353
|
+
args = aiServiceinstallArgParser.parse_args(args=argv)
|
|
354
|
+
|
|
355
|
+
# We use the presence of --mas-instance-id to determine whether
|
|
356
|
+
# the CLI is being started in interactive mode or not
|
|
357
|
+
instanceId = args.aibroker_instance_id
|
|
358
|
+
|
|
359
|
+
# Properties for arguments that control the behavior of the CLI
|
|
360
|
+
self.noConfirm = args.no_confirm
|
|
361
|
+
self.waitForPVC = not args.no_wait_for_pvc
|
|
362
|
+
self.licenseAccepted = args.accept_license
|
|
363
|
+
self.devMode = args.dev_mode
|
|
364
|
+
|
|
365
|
+
# Set image_pull_policy of the CLI in interactive mode
|
|
366
|
+
if args.image_pull_policy and args.image_pull_policy != "":
|
|
367
|
+
self.setParam("image_pull_policy", args.image_pull_policy)
|
|
368
|
+
|
|
369
|
+
self.approvals = {}
|
|
370
|
+
|
|
371
|
+
# Store all args
|
|
372
|
+
self.args = args
|
|
373
|
+
|
|
374
|
+
# These flags work for setting params in both interactive and non-interactive modes
|
|
375
|
+
if args.skip_pre_check:
|
|
376
|
+
self.setParam("skip_pre_check", "true")
|
|
377
|
+
|
|
378
|
+
if instanceId is None:
|
|
379
|
+
self.printH1("Set Target OpenShift Cluster")
|
|
380
|
+
# Connect to the target cluster
|
|
381
|
+
self.connect()
|
|
382
|
+
else:
|
|
383
|
+
logger.debug("Aiservice instance ID is set, so we assume already connected to the desired OCP")
|
|
384
|
+
self.lookupTargetArchitecture()
|
|
385
|
+
|
|
386
|
+
if self.dynamicClient is None:
|
|
387
|
+
print_formatted_text(HTML("<Red>Error: The Kubernetes dynamic Client is not available. See log file for details</Red>"))
|
|
388
|
+
exit(1)
|
|
389
|
+
|
|
390
|
+
# Perform a check whether the cluster is set up for airgap install, this will trigger an early failure if the cluster is using the now
|
|
391
|
+
# deprecated MaximoApplicationSuite ImageContentSourcePolicy instead of the new ImageDigestMirrorSet
|
|
392
|
+
self.isAirgap()
|
|
393
|
+
|
|
394
|
+
# Configure the installOptions for the appropriate architecture
|
|
395
|
+
self.catalogOptions = supportedCatalogs[self.architecture]
|
|
396
|
+
|
|
397
|
+
# Basic settings before the user provides any input
|
|
398
|
+
self.configICR()
|
|
399
|
+
self.deployCP4D = False
|
|
400
|
+
|
|
401
|
+
# UDS install has not been supported since the January 2024 catalog update
|
|
402
|
+
self.setParam("uds_action", "install-dro")
|
|
403
|
+
|
|
404
|
+
# User must either provide the configuration via numerous command line arguments, or the interactive prompts
|
|
405
|
+
if instanceId is None:
|
|
406
|
+
self.interactiveMode(simplified=args.simplified, advanced=args.advanced)
|
|
407
|
+
else:
|
|
408
|
+
self.nonInteractiveMode()
|
|
409
|
+
|
|
410
|
+
# Set up the sls license file
|
|
411
|
+
self.slsLicenseFile()
|
|
412
|
+
|
|
413
|
+
# Show a summary of the installation configuration
|
|
414
|
+
self.printH1("Non-Interactive Install Command")
|
|
415
|
+
self.printDescription([
|
|
416
|
+
"Save and re-use the following script to re-run this install without needing to answer the interactive prompts again",
|
|
417
|
+
"",
|
|
418
|
+
self.buildCommand()
|
|
419
|
+
])
|
|
420
|
+
|
|
421
|
+
self.displayInstallSummary()
|
|
422
|
+
|
|
423
|
+
if not self.noConfirm:
|
|
424
|
+
print()
|
|
425
|
+
self.printDescription([
|
|
426
|
+
"Please carefully review your choices above, correcting mistakes now is much easier than after the install has begun"
|
|
427
|
+
])
|
|
428
|
+
continueWithInstall = self.yesOrNo("Proceed with these settings")
|
|
429
|
+
|
|
430
|
+
# Prepare the namespace and launch the installation pipeline
|
|
431
|
+
if self.noConfirm or continueWithInstall:
|
|
432
|
+
self.createTektonFileWithDigest()
|
|
433
|
+
|
|
434
|
+
self.printH1("Launch Install")
|
|
435
|
+
pipelinesNamespace = f"mas-{self.getParam('aibroker_instance_id')}-pipelines"
|
|
436
|
+
|
|
437
|
+
if not self.noConfirm:
|
|
438
|
+
self.printDescription(["If you are using storage classes that utilize 'WaitForFirstConsumer' binding mode choose 'No' at the prompt below"])
|
|
439
|
+
wait = self.yesOrNo("Wait for PVCs to bind")
|
|
440
|
+
else:
|
|
441
|
+
wait = False
|
|
442
|
+
|
|
443
|
+
with Halo(text='Validating OpenShift Pipelines installation', spinner=self.spinner) as h:
|
|
444
|
+
installOpenShiftPipelines(self.dynamicClient)
|
|
445
|
+
h.stop_and_persist(symbol=self.successIcon, text="OpenShift Pipelines Operator is installed and ready to use")
|
|
446
|
+
|
|
447
|
+
with Halo(text=f'Preparing namespace ({pipelinesNamespace})', spinner=self.spinner) as h:
|
|
448
|
+
createNamespace(self.dynamicClient, pipelinesNamespace)
|
|
449
|
+
preparePipelinesNamespace(
|
|
450
|
+
dynClient=self.dynamicClient,
|
|
451
|
+
instanceId=self.getParam("aibroker_instance_id"),
|
|
452
|
+
storageClass=self.pipelineStorageClass,
|
|
453
|
+
accessMode=self.pipelineStorageAccessMode,
|
|
454
|
+
waitForBind=wait,
|
|
455
|
+
configureRBAC=(self.getParam("service_account_name") == "")
|
|
456
|
+
)
|
|
457
|
+
prepareInstallSecrets(
|
|
458
|
+
dynClient=self.dynamicClient,
|
|
459
|
+
instanceId=self.getParam("aibroker_instance_id"),
|
|
460
|
+
slsLicenseFile=self.slsLicenseFileSecret,
|
|
461
|
+
additionalConfigs=self.additionalConfigsSecret,
|
|
462
|
+
podTemplates=self.podTemplatesSecret,
|
|
463
|
+
certs=self.certsSecret
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
self.setupApprovals(pipelinesNamespace)
|
|
467
|
+
|
|
468
|
+
h.stop_and_persist(symbol=self.successIcon, text=f"Namespace is ready ({pipelinesNamespace})")
|
|
469
|
+
|
|
470
|
+
with Halo(text='Testing availability of MAS CLI image in cluster', spinner=self.spinner) as h:
|
|
471
|
+
testCLI()
|
|
472
|
+
h.stop_and_persist(symbol=self.successIcon, text="MAS CLI image deployment test completed")
|
|
473
|
+
|
|
474
|
+
with Halo(text=f'Installing latest Tekton definitions (v{self.version})', spinner=self.spinner) as h:
|
|
475
|
+
updateTektonDefinitions(pipelinesNamespace, self.tektonDefsPath)
|
|
476
|
+
h.stop_and_persist(symbol=self.successIcon, text=f"Latest Tekton definitions are installed (v{self.version})")
|
|
477
|
+
|
|
478
|
+
with Halo(text=f"Submitting PipelineRun for {self.getParam('aibroker_instance_id')} install", spinner=self.spinner) as h:
|
|
479
|
+
pipelineURL = launchInstallPipelineForAiservice(dynClient=self.dynamicClient, params=self.params)
|
|
480
|
+
if pipelineURL is not None:
|
|
481
|
+
h.stop_and_persist(symbol=self.successIcon, text=f"PipelineRun for {self.getParam('aibroker_instance_id')} install submitted")
|
|
482
|
+
print_formatted_text(HTML(f"\nView progress:\n <Cyan><u>{pipelineURL}</u></Cyan>\n"))
|
|
483
|
+
else:
|
|
484
|
+
h.stop_and_persist(symbol=self.failureIcon, text=f"Failed to submit PipelineRun for {self.getParam('aibroker_instance_id')} install, see log file for details")
|
|
485
|
+
print()
|
|
486
|
+
|
|
487
|
+
@logMethodCall
|
|
488
|
+
def setupApprovals(self, namespace: str) -> None:
|
|
489
|
+
"""
|
|
490
|
+
Ensure the supported approval configmaps are in the expected state for the start of the run:
|
|
491
|
+
- not present (if approval is not required)
|
|
492
|
+
- present with the chosen state field initialized to ""
|
|
493
|
+
"""
|
|
494
|
+
for approval in self.approvals.values():
|
|
495
|
+
if "maxRetries" in approval:
|
|
496
|
+
# Enable this approval workload
|
|
497
|
+
logger.debug(f"Approval workflow for {approval['id']} will be enabled during install ({approval['maxRetries']} / {approval['retryDelay']}s / {approval['ignoreFailure']})")
|
|
498
|
+
self.initializeApprovalConfigMap(namespace, approval['id'], True, approval['maxRetries'], approval['retryDelay'], approval['ignoreFailure'])
|
|
499
|
+
|
|
500
|
+
@logMethodCall
|
|
501
|
+
def chooseInstallFlavour(self) -> None:
|
|
502
|
+
# We don't have any configuration as Advanced options right now in Aibroker settings
|
|
503
|
+
# we can remove this chooseInstallFlavour - if we want...
|
|
504
|
+
self.printH1("Choose Install Mode")
|
|
505
|
+
self.printDescription([
|
|
506
|
+
"There are two flavours of the interactive install to choose from: <u>Simplified</u> and <u>Advanced</u>. The simplified option will present fewer dialogs, but you lose the ability to configure the following aspects of the installation:",
|
|
507
|
+
" - Configure dedicated License (AppPoints)"
|
|
508
|
+
])
|
|
509
|
+
self.showAdvancedOptions = self.yesOrNo("Show advanced installation options")
|
|
510
|
+
|
|
511
|
+
def aibrokerSettings(self) -> None:
|
|
512
|
+
if self.installAiBroker:
|
|
513
|
+
self.printH2("AI Service Settings - Storage, WatsonX, MariaDB details")
|
|
514
|
+
self.printDescription(["Customise AI Broker details"])
|
|
515
|
+
self.promptForString("Storage provider", "mas_aibroker_storage_provider")
|
|
516
|
+
self.promptForString("Storage access key", "mas_aibroker_storage_accesskey")
|
|
517
|
+
self.promptForString("Storage secret key", "mas_aibroker_storage_secretkey", isPassword=True)
|
|
518
|
+
self.promptForString("Storage host", "mas_aibroker_storage_host")
|
|
519
|
+
self.promptForString("Storage port", "mas_aibroker_storage_port")
|
|
520
|
+
self.promptForString("Storage ssl", "mas_aibroker_storage_ssl")
|
|
521
|
+
self.promptForString("Storage region", "mas_aibroker_storage_region")
|
|
522
|
+
self.promptForString("Storage pipelines bucket", "mas_aibroker_storage_pipelines_bucket")
|
|
523
|
+
self.promptForString("Storage tenants bucket", "mas_aibroker_storage_tenants_bucket")
|
|
524
|
+
self.promptForString("Storage templates bucket", "mas_aibroker_storage_templates_bucket")
|
|
525
|
+
|
|
526
|
+
self.promptForString("Watsonxai api key", "mas_aibroker_watsonxai_apikey", isPassword=True)
|
|
527
|
+
self.promptForString("Watsonxai machine learning url", "mas_aibroker_watsonxai_url")
|
|
528
|
+
self.promptForString("Watsonxai project id", "mas_aibroker_watsonxai_project_id")
|
|
529
|
+
|
|
530
|
+
self.promptForString("Database host", "mas_aibroker_db_host")
|
|
531
|
+
self.promptForString("Database port", "mas_aibroker_db_port")
|
|
532
|
+
self.promptForString("Database user", "mas_aibroker_db_user")
|
|
533
|
+
self.promptForString("Database name", "mas_aibroker_db_database")
|
|
534
|
+
self.promptForString("Database Secretname", "mas_aibroker_db_secret_name", isPassword=True)
|
|
535
|
+
self.promptForString("Database password", "mas_aibroker_db_secret_value", isPassword=True)
|
|
536
|
+
|
|
537
|
+
if self.getParam("mas_app_channel_aibroker") != "9.0.x":
|
|
538
|
+
self.promptForString("Mariadb username", "mariadb_user")
|
|
539
|
+
self.promptForString("Mariadb password", "mariadb_password", isPassword=True)
|
|
540
|
+
self.promptForString("Tenant entitlement type", "tenant_entitlement_type")
|
|
541
|
+
self.promptForString("Tenant start date", "tenant_entitlement_start_date")
|
|
542
|
+
self.promptForString("Tenant end date", "tenant_entitlement_end_date")
|
|
543
|
+
self.promptForString("S3 bucket prefix", "mas_aibroker_s3_bucket_prefix")
|
|
544
|
+
self.promptForString("S3 endpoint url", "mas_aibroker_s3_endpoint_url")
|
|
545
|
+
self.promptForString("S3 bucket prefix (tenant level)", "mas_aibroker_tenant_s3_bucket_prefix")
|
|
546
|
+
self.promptForString("S3 region (tenant level)", "mas_aibroker_tenant_s3_region")
|
|
547
|
+
self.promptForString("S3 endpoint url (tenant level)", "mas_aibroker_tenant_s3_endpoint_url")
|
|
548
|
+
self.promptForString("S3 access key (tenant level)", "mas_aibroker_tenant_s3_access_key", isPassword=True)
|
|
549
|
+
self.promptForString("S3 secret key (tenant level)", "mas_aibroker_tenant_s3_secret_key", isPassword=True)
|
|
550
|
+
self.promptForString("RSL url", "rsl_url")
|
|
551
|
+
self.promptForString("ORG Id of RSL", "rsl_org_id")
|
|
552
|
+
self.promptForString("Token for RSL", "rsl_token", isPassword=True)
|
|
553
|
+
self.yesOrNo("Install minio", "install_minio_aiservice")
|
|
554
|
+
if self.getParam("install_minio_aiservice") == "true":
|
|
555
|
+
self.promptForString("minio root username", "minio_root_user")
|
|
556
|
+
self.promptForString("minio root password", "minio_root_password", isPassword=True)
|
|
557
|
+
self.yesOrNo("Install SLS", "install_sls_aiservice")
|
|
558
|
+
if self.getParam("install_sls_aiservice") != "true":
|
|
559
|
+
self.promptForString("SLS secret name", "mas_aibroker_sls_secret_name")
|
|
560
|
+
self.promptForString("SLS registration key", "mas_aibroker_sls_registration_key")
|
|
561
|
+
self.promptForString("SLS URL", "mas_aibroker_sls_url")
|
|
562
|
+
self.promptForString("SLS CA certificate", "mas_aibroker_sls_ca_cert")
|
|
563
|
+
self.yesOrNo("Install DRO", "install_dro_aiservice")
|
|
564
|
+
if self.getParam("install_dro_aiservice") != "true":
|
|
565
|
+
self.promptForString("DRO secret name", "mas_aibroker_dro_secret_name")
|
|
566
|
+
self.promptForString("DRO API key", "mas_aibroker_dro_api_key")
|
|
567
|
+
self.promptForString("DRO URL", "mas_aibroker_dro_url")
|
|
568
|
+
self.promptForString("DRO CA certificate", "mas_aibroker_dro_ca_cert")
|
|
569
|
+
self.yesOrNo("Install DB2", "install_db2_aiservice")
|
|
570
|
+
if self.getParam("install_db2_aiservice") != "true":
|
|
571
|
+
self.promptForString("DB2 username", "mas_aibroker_db2_username")
|
|
572
|
+
self.promptForString("DB2 password", "mas_aibroker_db2_password")
|
|
573
|
+
self.promptForString("DB2 JDBC URL", "mas_aibroker_db2_jdbc_url")
|
|
574
|
+
self.promptForString("DB2 SSL enabled (yes/no)", "mas_aibroker_db2_ssl_enabled")
|
|
575
|
+
self.promptForString("DB2 CA certificate", "mas_aibroker_db2_ca_cert")
|
|
576
|
+
# self.promptForString("Environment type", "environment_type")
|
|
577
|
+
|
|
578
|
+
# These are all candidates to centralise in a new mixin used by both install and aiservice-install
|
|
579
|
+
|
|
580
|
+
@logMethodCall
|
|
581
|
+
def configICR(self):
|
|
582
|
+
if self.devMode:
|
|
583
|
+
self.setParam("mas_icr_cp", getenv("MAS_ICR_CP", "docker-na-public.artifactory.swg-devops.com/wiotp-docker-local"))
|
|
584
|
+
self.setParam("mas_icr_cpopen", getenv("MAS_ICR_CPOPEN", "docker-na-public.artifactory.swg-devops.com/wiotp-docker-local/cpopen"))
|
|
585
|
+
self.setParam("sls_icr_cpopen", getenv("SLS_ICR_CPOPEN", "docker-na-public.artifactory.swg-devops.com/wiotp-docker-local/cpopen"))
|
|
586
|
+
else:
|
|
587
|
+
self.setParam("mas_icr_cp", getenv("MAS_ICR_CP", "cp.icr.io/cp"))
|
|
588
|
+
self.setParam("mas_icr_cpopen", getenv("MAS_ICR_CPOPEN", "icr.io/cpopen"))
|
|
589
|
+
self.setParam("sls_icr_cpopen", getenv("SLS_ICR_CPOPEN", "icr.io/cpopen"))
|
|
590
|
+
|
|
591
|
+
@logMethodCall
|
|
592
|
+
def configICRCredentials(self):
|
|
593
|
+
self.printH1("Configure IBM Container Registry")
|
|
594
|
+
self.promptForString("IBM entitlement key", "ibm_entitlement_key", isPassword=True)
|
|
595
|
+
if self.devMode:
|
|
596
|
+
self.promptForString("Artifactory username", "artifactory_username")
|
|
597
|
+
self.promptForString("Artifactory token", "artifactory_token", isPassword=True)
|
|
598
|
+
|
|
599
|
+
@logMethodCall
|
|
600
|
+
def configCertManager(self):
|
|
601
|
+
# Only install of Red Hat Cert-Manager has been supported since the January 2025 catalog update
|
|
602
|
+
self.setParam("cert_manager_provider", "redhat")
|
|
603
|
+
self.setParam("cert_manager_action", "install")
|
|
604
|
+
|
|
605
|
+
def formatCatalog(self, name: str) -> str:
|
|
606
|
+
# Convert "v9-241107-amd64" into "November 2024 Update (v9-241107-amd64)"
|
|
607
|
+
date = name.split("-")[1]
|
|
608
|
+
month = int(date[2:4])
|
|
609
|
+
monthName = calendar.month_name[month]
|
|
610
|
+
year = date[:2]
|
|
611
|
+
return f" - {monthName} 20{year} Update\n <Orange><u>https://ibm-mas.github.io/cli/catalogs/{name}</u></Orange>"
|
|
612
|
+
|
|
613
|
+
@logMethodCall
|
|
614
|
+
def configCatalog(self):
|
|
615
|
+
self.printH1("IBM Maximo Operator Catalog Selection")
|
|
616
|
+
if self.devMode:
|
|
617
|
+
self.promptForString("Select catalog source", "mas_catalog_version", default="v9-master-amd64")
|
|
618
|
+
else:
|
|
619
|
+
catalogInfo = getCurrentCatalog(self.dynamicClient)
|
|
620
|
+
|
|
621
|
+
if catalogInfo is None:
|
|
622
|
+
self.printDescription([
|
|
623
|
+
"The catalog you choose dictates the version of everything that is installed, with Maximo Application Suite this is the only version you need to remember; all other versions are determined by this choice.",
|
|
624
|
+
"Older catalogs can still be used, but we recommend using an older version of the CLI that aligns with the release date of the catalog.",
|
|
625
|
+
" - Learn more: <Orange><u>https://ibm-mas.github.io/cli/catalogs/</u></Orange>",
|
|
626
|
+
""
|
|
627
|
+
])
|
|
628
|
+
print("Supported Catalogs:")
|
|
629
|
+
for catalog in self.catalogOptions:
|
|
630
|
+
catalogString = self.formatCatalog(catalog)
|
|
631
|
+
print_formatted_text(HTML(f"{catalogString}"))
|
|
632
|
+
print()
|
|
633
|
+
|
|
634
|
+
catalogCompleter = WordCompleter(self.catalogOptions)
|
|
635
|
+
catalogSelection = self.promptForString("Select catalog", completer=catalogCompleter)
|
|
636
|
+
self.setParam("mas_catalog_version", catalogSelection)
|
|
637
|
+
else:
|
|
638
|
+
self.printDescription([
|
|
639
|
+
f"The IBM Maximo Operator Catalog is already installed in this cluster ({catalogInfo['catalogId']}). If you wish to install MAS using a newer version of the catalog please first update the catalog using mas update."
|
|
640
|
+
])
|
|
641
|
+
self.setParam("mas_catalog_version", catalogInfo["catalogId"])
|
|
642
|
+
|
|
643
|
+
self.chosenCatalog = getCatalog(self.getParam("mas_catalog_version"))
|
|
644
|
+
catalogSummary = self.processCatalogChoice()
|
|
645
|
+
self.printDescription(catalogSummary)
|
|
646
|
+
self.printDescription([
|
|
647
|
+
"",
|
|
648
|
+
"Two types of release are available:",
|
|
649
|
+
" - GA releases of Maximo Application Suite are supported under IBM's standard 3+1+3 support lifecycle policy.",
|
|
650
|
+
" - 'Feature' releases allow early access to new features for evaluation in non-production environments and are only supported through to the next GA release.",
|
|
651
|
+
""
|
|
652
|
+
])
|
|
653
|
+
|
|
654
|
+
print(tabulate(self.catalogTable, headers="keys", tablefmt="simple_grid"))
|
|
655
|
+
|
|
656
|
+
releaseCompleter = WordCompleter(sorted(self.catalogReleases, reverse=True))
|
|
657
|
+
releaseSelection = self.promptForString("Select release", completer=releaseCompleter)
|
|
658
|
+
|
|
659
|
+
self.setParam("mas_app_channel_aibroker", self.catalogReleases[releaseSelection])
|
|
660
|
+
|
|
661
|
+
@logMethodCall
|
|
662
|
+
def validateCatalogSource(self):
|
|
663
|
+
catalogsAPI = self.dynamicClient.resources.get(api_version="operators.coreos.com/v1alpha1", kind="CatalogSource")
|
|
664
|
+
try:
|
|
665
|
+
catalog = catalogsAPI.get(name="ibm-operator-catalog", namespace="openshift-marketplace")
|
|
666
|
+
catalogDisplayName = catalog.spec.displayName
|
|
667
|
+
|
|
668
|
+
m = re.match(r".+(?P<catalogId>v[89]-(?P<catalogVersion>[0-9]+)-amd64)", catalogDisplayName)
|
|
669
|
+
if m:
|
|
670
|
+
# catalogId = v8-yymmdd-amd64
|
|
671
|
+
# catalogVersion = yymmdd
|
|
672
|
+
catalogId = m.group("catalogId")
|
|
673
|
+
elif re.match(r".+v8-amd64", catalogDisplayName):
|
|
674
|
+
catalogId = "v8-amd64"
|
|
675
|
+
else:
|
|
676
|
+
self.fatalError(f"IBM Maximo Operator Catalog is already installed on this cluster. However, it is not possible to identify its version. If you wish to install a new MAS instance using the {self.getParam('mas_catalog_version')} catalog please first run 'mas update' to switch to this catalog, this will ensure the appropriate actions are performed as part of the catalog update")
|
|
677
|
+
|
|
678
|
+
if catalogId != self.getParam("mas_catalog_version"):
|
|
679
|
+
self.fatalError(f"IBM Maximo Operator Catalog {catalogId} is already installed on this cluster, if you wish to install a new MAS instance using the {self.getParam('mas_catalog_version')} catalog please first run 'mas update' to switch to this catalog, this will ensure the appropriate actions are performed as part of the catalog update")
|
|
680
|
+
except NotFoundError:
|
|
681
|
+
# There's no existing catalog installed
|
|
682
|
+
pass
|
|
683
|
+
|
|
684
|
+
# TODO: update licenses for aiservice 9.1.x
|
|
685
|
+
@logMethodCall
|
|
686
|
+
def licensePrompt(self):
|
|
687
|
+
if not self.licenseAccepted:
|
|
688
|
+
self.printH1("License Terms")
|
|
689
|
+
self.printDescription([
|
|
690
|
+
"To continue with the installation, you must accept the license terms:",
|
|
691
|
+
self.licenses[f"aibroker-{self.getParam('mas_app_channel_aibroker')}"]
|
|
692
|
+
])
|
|
693
|
+
|
|
694
|
+
if self.noConfirm:
|
|
695
|
+
self.fatalError("You must accept the license terms with --accept-license when using the --no-confirm flag")
|
|
696
|
+
else:
|
|
697
|
+
if not self.yesOrNo("Do you accept the license terms"):
|
|
698
|
+
exit(1)
|
|
699
|
+
|
|
700
|
+
@logMethodCall
|
|
701
|
+
def configStorageClasses(self):
|
|
702
|
+
self.printH1("Configure Storage Class Usage")
|
|
703
|
+
self.printDescription([
|
|
704
|
+
"Maximo Application Suite and it's dependencies require storage classes that support ReadWriteOnce (RWO) and ReadWriteMany (RWX) access modes:",
|
|
705
|
+
" - ReadWriteOnce volumes can be mounted as read-write by multiple pods on a single node.",
|
|
706
|
+
" - ReadWriteMany volumes can be mounted as read-write by multiple pods across many nodes.",
|
|
707
|
+
""
|
|
708
|
+
])
|
|
709
|
+
defaultStorageClasses = getDefaultStorageClasses(self.dynamicClient)
|
|
710
|
+
if defaultStorageClasses.provider is not None:
|
|
711
|
+
print_formatted_text(HTML(f"<MediumSeaGreen>Storage provider auto-detected: {defaultStorageClasses.providerName}</MediumSeaGreen>"))
|
|
712
|
+
print_formatted_text(HTML(f"<LightSlateGrey> - Storage class (ReadWriteOnce): {defaultStorageClasses.rwo}</LightSlateGrey>"))
|
|
713
|
+
print_formatted_text(HTML(f"<LightSlateGrey> - Storage class (ReadWriteMany): {defaultStorageClasses.rwx}</LightSlateGrey>"))
|
|
714
|
+
self.storageClassProvider = defaultStorageClasses.provider
|
|
715
|
+
self.params["storage_class_rwo"] = defaultStorageClasses.rwo
|
|
716
|
+
self.params["storage_class_rwx"] = defaultStorageClasses.rwx
|
|
717
|
+
|
|
718
|
+
overrideStorageClasses = False
|
|
719
|
+
if "storage_class_rwx" in self.params and self.params["storage_class_rwx"] != "":
|
|
720
|
+
overrideStorageClasses = not self.yesOrNo("Use the auto-detected storage classes")
|
|
721
|
+
|
|
722
|
+
if "storage_class_rwx" not in self.params or self.params["storage_class_rwx"] == "" or overrideStorageClasses:
|
|
723
|
+
self.storageClassProvider = "custom"
|
|
724
|
+
|
|
725
|
+
self.printDescription([
|
|
726
|
+
"Select the ReadWriteOnce and ReadWriteMany storage classes to use from the list below:",
|
|
727
|
+
"Enter 'none' for the ReadWriteMany storage class if you do not have a suitable class available in the cluster, however this will limit what can be installed"
|
|
728
|
+
])
|
|
729
|
+
for storageClass in getStorageClasses(self.dynamicClient):
|
|
730
|
+
print_formatted_text(HTML(f"<LightSlateGrey> - {storageClass.metadata.name}</LightSlateGrey>"))
|
|
731
|
+
|
|
732
|
+
self.params["storage_class_rwo"] = prompt(HTML('<Yellow>ReadWriteOnce (RWO) storage class</Yellow> '), validator=StorageClassValidator(), validate_while_typing=False)
|
|
733
|
+
self.params["storage_class_rwx"] = prompt(HTML('<Yellow>ReadWriteMany (RWX) storage class</Yellow> '), validator=StorageClassValidator(), validate_while_typing=False)
|
|
734
|
+
|
|
735
|
+
# Configure storage class for pipeline PVC
|
|
736
|
+
# We prefer to use ReadWriteMany, but we can cope with ReadWriteOnce if necessary
|
|
737
|
+
if self.isSNO() or self.params["storage_class_rwx"] == "none":
|
|
738
|
+
self.pipelineStorageClass = self.getParam("storage_class_rwo")
|
|
739
|
+
self.pipelineStorageAccessMode = "ReadWriteOnce"
|
|
740
|
+
else:
|
|
741
|
+
self.pipelineStorageClass = self.getParam("storage_class_rwx")
|
|
742
|
+
self.pipelineStorageAccessMode = "ReadWriteMany"
|
|
743
|
+
|
|
744
|
+
@logMethodCall
|
|
745
|
+
def configSLS(self) -> None:
|
|
746
|
+
self.printH1("Configure AppPoint Licensing")
|
|
747
|
+
self.printDescription(
|
|
748
|
+
[
|
|
749
|
+
"By default the MAS instance will be configured to use a cluster-shared License, this provides a shared pool of AppPoints available to all MAS instances on the cluster.",
|
|
750
|
+
"",
|
|
751
|
+
]
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
self.slsMode = 1
|
|
755
|
+
self.slsLicenseFileLocal = None
|
|
756
|
+
|
|
757
|
+
if self.showAdvancedOptions:
|
|
758
|
+
self.printDescription(
|
|
759
|
+
[
|
|
760
|
+
"Alternatively you may choose to install using a dedicated license only available to this MAS instance.",
|
|
761
|
+
" 1. Install MAS with Cluster-Shared License (AppPoints)",
|
|
762
|
+
" 2. Install MAS with Dedicated License (AppPoints)",
|
|
763
|
+
]
|
|
764
|
+
)
|
|
765
|
+
self.slsMode = self.promptForInt("SLS Mode", default=1)
|
|
766
|
+
|
|
767
|
+
if self.slsMode not in [1, 2]:
|
|
768
|
+
self.fatalError(f"Invalid selection: {self.slsMode}")
|
|
769
|
+
|
|
770
|
+
if not (self.slsMode == 2 and not self.getParam("sls_namespace")):
|
|
771
|
+
sls_namespace = "ibm-sls" if self.slsMode == 1 else self.getParam("sls_namespace")
|
|
772
|
+
if findSLSByNamespace(sls_namespace, dynClient=self.dynamicClient):
|
|
773
|
+
print_formatted_text(HTML(f"<MediumSeaGreen>SLS auto-detected: {sls_namespace}</MediumSeaGreen>"))
|
|
774
|
+
print()
|
|
775
|
+
if not self.yesOrNo("Upload/Replace the license file"):
|
|
776
|
+
self.setParam("sls_action", "gencfg")
|
|
777
|
+
return
|
|
778
|
+
|
|
779
|
+
self.slsLicenseFileLocal = self.promptForFile("License file", mustExist=True, envVar="SLS_LICENSE_FILE_LOCAL")
|
|
780
|
+
self.setParam("sls_action", "install")
|
|
781
|
+
|
|
782
|
+
@logMethodCall
|
|
783
|
+
def configDRO(self) -> None:
|
|
784
|
+
self.promptForString("Contact e-mail address", "uds_contact_email")
|
|
785
|
+
self.promptForString("Contact first name", "uds_contact_firstname")
|
|
786
|
+
self.promptForString("Contact last name", "uds_contact_lastname")
|
|
787
|
+
|
|
788
|
+
if self.showAdvancedOptions:
|
|
789
|
+
self.promptForString("IBM Data Reporter Operator (DRO) Namespace", "dro_namespace", default="redhat-marketplace")
|
|
790
|
+
|
|
791
|
+
@logMethodCall
|
|
792
|
+
def configAppChannel(self, appId):
|
|
793
|
+
self.params[f"mas_app_channel_{appId}"] = prompt(HTML('<Yellow>Custom channel for Aibroker</Yellow> '))
|
|
794
|
+
|
|
795
|
+
@logMethodCall
|
|
796
|
+
def configOperationMode(self):
|
|
797
|
+
self.printH1("Configure Operational Mode")
|
|
798
|
+
self.printDescription([
|
|
799
|
+
"Maximo Application Suite can be installed in a non-production mode for internal development and testing, this setting cannot be changed after installation:",
|
|
800
|
+
" - All applications, add-ons, and solutions have 0 (zero) installation AppPoints in non-production installations.",
|
|
801
|
+
" - These specifications are also visible in the metrics that are shared with IBM and in the product UI.",
|
|
802
|
+
"",
|
|
803
|
+
" 1. Production",
|
|
804
|
+
" 2. Non-Production"
|
|
805
|
+
])
|
|
806
|
+
self.operationalMode = self.promptForInt("Operational Mode", default=1)
|
|
807
|
+
if self.operationalMode == 1:
|
|
808
|
+
self.setParam("environment_type", "production")
|
|
809
|
+
else:
|
|
810
|
+
self.setParam("environment_type", "non-production")
|