mas-cli 9.5.0__py3-none-any.whl → 10.0.1__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/cli.py +82 -65
- mas/cli/displayMixins.py +110 -0
- mas/cli/gencfg.py +65 -0
- mas/cli/install/__init__.py +9 -0
- mas/cli/install/argParser.py +792 -0
- mas/cli/install/settings/__init__.py +21 -0
- mas/cli/install/settings/additionalConfigs.py +150 -0
- mas/cli/install/settings/db2Settings.py +173 -0
- mas/cli/install/settings/kafkaSettings.py +103 -0
- mas/cli/install/settings/manageSettings.py +218 -0
- mas/cli/install/settings/turbonomicSettings.py +25 -0
- mas/cli/install/summarizer.py +328 -0
- mas/cli/templates/ibm-mas-tekton.yaml +10813 -0
- mas/cli/templates/jdbccfg.yml.j2 +52 -0
- mas/cli/templates/pod-templates/best-effort/ibm-data-dictionary-assetdatadictionary.yml +26 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-bascfg.yml +56 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-coreidp.yml +21 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-actions.yml +28 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-auth.yml +32 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-datapower.yml +12 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-devops.yml +14 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-dm.yml +22 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-dsc.yml +40 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-edgeconfig.yml +10 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-fpl.yml +24 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-guardian.yml +20 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-iot.yml +10 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-mbgx.yml +18 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-mfgx.yml +14 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-monitor.yml +18 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-orgmgmt.yml +48 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-provision.yml +28 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-registry.yml +26 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-state.yml +40 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-webui.yml +22 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-healthextaccelerator.yml +13 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-healthextworkspace.yml +10 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-imagestitching.yml +10 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-manageaccelerators.yml +10 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-manageapp.yml +46 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-manageworkspace.yml +48 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-slackproxy.yml +10 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-pushnotificationcfg.yml +13 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-scimcfg.yml +14 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-slscfg.yml +10 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-smtpcfg.yml +10 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-suite.yml +136 -0
- mas/cli/templates/pod-templates/best-effort/ibm-mas-visualinspection.yml +34 -0
- mas/cli/templates/pod-templates/best-effort/ibm-sls-licenseservice.yml +10 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-data-dictionary-assetdatadictionary.yml +56 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-bascfg.yml +140 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-coreidp.yml +45 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-actions.yml +70 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-auth.yml +80 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-datapower.yml +24 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-devops.yml +26 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-dm.yml +52 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-dsc.yml +106 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-edgeconfig.yml +16 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-fpl.yml +62 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-guardian.yml +44 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-iot.yml +16 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-mbgx.yml +42 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-mfgx.yml +32 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-monitor.yml +42 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-orgmgmt.yml +126 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-provision.yml +70 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-registry.yml +62 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-state.yml +106 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-webui.yml +52 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-healthextaccelerator.yml +28 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-healthextworkspace.yml +18 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-imagestitching.yml +16 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-manageaccelerators.yml +16 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-manageapp.yml +106 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-manageworkspace.yml +126 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-slackproxy.yml +16 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-pushnotificationcfg.yml +25 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-scimcfg.yml +26 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-slscfg.yml +16 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-smtpcfg.yml +16 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-suite.yml +340 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-mas-visualinspection.yml +76 -0
- mas/cli/templates/pod-templates/guaranteed/ibm-sls-licenseservice.yml +16 -0
- mas/cli/templates/pod-templates/saas-essentials/ibm-mas-visualinspection.yml +46 -0
- mas/cli/validators.py +126 -0
- mas_cli-10.0.1.data/scripts/mas-install +978 -0
- {mas_cli-9.5.0.data → mas_cli-10.0.1.data}/scripts/mas-uninstall +11 -8
- {mas_cli-9.5.0.data → mas_cli-10.0.1.data}/scripts/mas-upgrade +6 -4
- {mas_cli-9.5.0.dist-info → mas_cli-10.0.1.dist-info}/METADATA +2 -1
- mas_cli-10.0.1.dist-info/RECORD +94 -0
- mas_cli-9.5.0.dist-info/RECORD +0 -8
- {mas_cli-9.5.0.dist-info → mas_cli-10.0.1.dist-info}/WHEEL +0 -0
- {mas_cli-9.5.0.dist-info → mas_cli-10.0.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
#!python
|
|
2
|
+
# *****************************************************************************
|
|
3
|
+
# Copyright (c) 2024 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
|
+
import logging.handlers
|
|
14
|
+
from sys import exit
|
|
15
|
+
from os import path
|
|
16
|
+
import re
|
|
17
|
+
|
|
18
|
+
from openshift.dynamic.exceptions import NotFoundError
|
|
19
|
+
|
|
20
|
+
from prompt_toolkit import prompt, print_formatted_text, HTML
|
|
21
|
+
from urllib3.exceptions import MaxRetryError
|
|
22
|
+
from jinja2.exceptions import TemplateNotFound
|
|
23
|
+
from kubeconfig.exceptions import KubectlCommandError
|
|
24
|
+
from kubernetes.client.exceptions import ApiException
|
|
25
|
+
|
|
26
|
+
from tabulate import tabulate
|
|
27
|
+
|
|
28
|
+
from halo import Halo
|
|
29
|
+
|
|
30
|
+
from mas.cli.cli import BaseApp
|
|
31
|
+
from mas.cli.gencfg import ConfigGeneratorMixin
|
|
32
|
+
from mas.cli.install.argParser import installArgParser
|
|
33
|
+
from mas.cli.install.settings import InstallSettingsMixin
|
|
34
|
+
from mas.cli.install.summarizer import InstallSummarizerMixin
|
|
35
|
+
|
|
36
|
+
from mas.cli.validators import (
|
|
37
|
+
InstanceIDFormatValidator,
|
|
38
|
+
WorkspaceIDFormatValidator,
|
|
39
|
+
WorkspaceNameFormatValidator,
|
|
40
|
+
StorageClassValidator,
|
|
41
|
+
OptimizerInstallPlanValidator
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
from mas.devops.ocp import createNamespace, getStorageClass, getStorageClasses
|
|
45
|
+
from mas.devops.tekton import installOpenShiftPipelines, updateTektonDefinitions, preparePipelinesNamespace, prepareInstallSecrets, testCLI, launchInstallPipeline
|
|
46
|
+
|
|
47
|
+
logger = logging.getLogger(__name__)
|
|
48
|
+
|
|
49
|
+
class App(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGeneratorMixin):
|
|
50
|
+
def validateCatalogSource(self):
|
|
51
|
+
catalogsAPI = self.dynamicClient.resources.get(api_version="operators.coreos.com/v1alpha1", kind="CatalogSource")
|
|
52
|
+
try:
|
|
53
|
+
catalog = catalogsAPI.get(name="ibm-operator-catalog", namespace="openshift-marketplace")
|
|
54
|
+
catalogDisplayName = catalog.spec.displayName
|
|
55
|
+
catalogImage = catalog.spec.image
|
|
56
|
+
|
|
57
|
+
m = re.match(r".+(?P<catalogId>v[89]-(?P<catalogVersion>[0-9]+)-amd64)", catalogDisplayName)
|
|
58
|
+
if m:
|
|
59
|
+
# catalogId = v8-yymmdd-amd64
|
|
60
|
+
# catalogVersion = yymmdd
|
|
61
|
+
catalogId = m.group("catalogId")
|
|
62
|
+
catalogVersion = m.group("catalogVersion")
|
|
63
|
+
elif re.match(r".+v8-amd64", catalogDisplayName):
|
|
64
|
+
catalogId = "v8-amd64"
|
|
65
|
+
catalogVersion = "v8-amd64"
|
|
66
|
+
|
|
67
|
+
if catalogId != self.getParam("mas_catalog_version"):
|
|
68
|
+
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")
|
|
69
|
+
except NotFoundError:
|
|
70
|
+
# There's no existing catalog installed
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
def validateInternalRegistryAvailable(self):
|
|
74
|
+
"""
|
|
75
|
+
We can save customers wasted time by detecting if the image-registry service
|
|
76
|
+
is available in the cluster. If it's not, and they've selected to install
|
|
77
|
+
Manage then their install is going to fail, so let's just prevent the install
|
|
78
|
+
starting in the first place.
|
|
79
|
+
"""
|
|
80
|
+
serviceAPI = self.dynamicClient.resources.get(api_version="v1", kind="Service")
|
|
81
|
+
try:
|
|
82
|
+
serviceAPI.get(name="image-registry", namespace="openshift-image-registry")
|
|
83
|
+
except NotFoundError:
|
|
84
|
+
self.fatalError(
|
|
85
|
+
"\n".join[
|
|
86
|
+
"Unable to proceed with installation of Maximo Manage. Could not detect the required \"image-registry\" service in the openshift-image-registry namespace",
|
|
87
|
+
"For more information refer to <u>https://www.ibm.com/docs/en/masv-and-l/continuous-delivery?topic=installing-enabling-openshift-internal-image-registry</u>"
|
|
88
|
+
])
|
|
89
|
+
|
|
90
|
+
def licensePrompt(self):
|
|
91
|
+
licenses = {
|
|
92
|
+
"8.9.x": " - <u>https://ibm.biz/MAS89-License</u>",
|
|
93
|
+
"8.10.x": " - <u>https://ibm.biz/MAS810-License</u>",
|
|
94
|
+
"8.11.x": " - <u>https://ibm.biz/MAS811-License</u>\n - <u>https://ibm.biz/MAXIT81-License</u>",
|
|
95
|
+
"9.0.x": " - <u>https://ibm.biz/MAS90-License</u>\n - <u>https://ibm.biz/MaximoIT90-License</u>\n - <u>https://ibm.biz/MAXArcGIS90-License</u>"
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if not self.licenseAccepted:
|
|
99
|
+
self.printH1("License Terms")
|
|
100
|
+
self.printDescription([
|
|
101
|
+
"To continue with the installation, you must accept the license terms:",
|
|
102
|
+
licenses[self.getParam('mas_channel')]
|
|
103
|
+
])
|
|
104
|
+
|
|
105
|
+
if self.noConfirm:
|
|
106
|
+
self.fatalError("You must accept the license terms with --accept-license when using the --no-confirm flag")
|
|
107
|
+
else:
|
|
108
|
+
if not self.yesOrNo("Do you accept the license terms"):
|
|
109
|
+
exit(1)
|
|
110
|
+
|
|
111
|
+
def configICR(self):
|
|
112
|
+
if self.args.dev_mode:
|
|
113
|
+
self.setParam("mas_icr_cp", "docker-na-public.artifactory.swg-devops.com/wiotp-docker-local")
|
|
114
|
+
self.setParam("mas_icr_cpopen", "docker-na-public.artifactory.swg-devops.com/wiotp-docker-local/cpopen")
|
|
115
|
+
self.setParam("sls_icr_cpopen", "docker-na-public.artifactory.swg-devops.com/wiotp-docker-local/cpopen")
|
|
116
|
+
else:
|
|
117
|
+
self.setParam("mas_icr_cp", "cp.icr.io/cp")
|
|
118
|
+
self.setParam("mas_icr_cpopen", "icr.io/cpopen")
|
|
119
|
+
self.setParam("sls_icr_cpopen", "icr.io/cpopen")
|
|
120
|
+
|
|
121
|
+
def configICRCredentials(self):
|
|
122
|
+
self.printH1("Configure IBM Container Registry")
|
|
123
|
+
self.promptForString("IBM entitlement key", "ibm_entitlement_key", isPassword=True)
|
|
124
|
+
if self.args.dev_mode:
|
|
125
|
+
self.promptForString("Artifactory username", "artifactory_username", isPassword=True)
|
|
126
|
+
self.promptForString("Artifactory token", "artifactory_token", isPassword=True)
|
|
127
|
+
|
|
128
|
+
def configCertManager(self):
|
|
129
|
+
# Only install of Red Hat Cert-Manager has been supported since the January 2025 catalog update
|
|
130
|
+
self.setParam("cert_manager_provider", "redhat")
|
|
131
|
+
self.setParam("cert_manager_action", "install")
|
|
132
|
+
|
|
133
|
+
def configCatalog(self):
|
|
134
|
+
self.printH1("IBM Maximo Operator Catalog Selection")
|
|
135
|
+
if self.args.dev_mode:
|
|
136
|
+
self.promptForString("Select catalog source", "mas_catalog_version", default="v9-master-amd64")
|
|
137
|
+
self.promptForString("Select channel", "mas_channel", default="9.0.x-dev")
|
|
138
|
+
else:
|
|
139
|
+
print(tabulate(self.installOptions, headers="keys", tablefmt="simple_grid"))
|
|
140
|
+
catalogSelection = self.promptForInt("Select catalog and release", default=1)
|
|
141
|
+
|
|
142
|
+
self.setParam("mas_catalog_version", self.installOptions[catalogSelection-1]["catalog"])
|
|
143
|
+
self.setParam("mas_channel", self.installOptions[catalogSelection-1]["release"])
|
|
144
|
+
|
|
145
|
+
def configSLS(self) -> None:
|
|
146
|
+
self.printH1("Configure Product License")
|
|
147
|
+
self.slsLicenseFileLocal = self.promptForFile("License file", mustExist=True)
|
|
148
|
+
self.promptForString("Contact e-mail address", "uds_contact_email")
|
|
149
|
+
self.promptForString("Contact first name", "uds_contact_firstname")
|
|
150
|
+
self.promptForString("Contact last name", "uds_contact_lastname")
|
|
151
|
+
|
|
152
|
+
self.promptForString("IBM Data Reporter Operator (DRO) Namespace", "dro_namespace", default="redhat-marketplace")
|
|
153
|
+
|
|
154
|
+
def selectLocalConfigDir(self) -> None:
|
|
155
|
+
if self.localConfigDir is None:
|
|
156
|
+
# You need to tell us where the configuration file can be found
|
|
157
|
+
self.localConfigDir = self.promptForDir("Select Local configuration directory")
|
|
158
|
+
|
|
159
|
+
def configGrafana(self) -> None:
|
|
160
|
+
try:
|
|
161
|
+
packagemanifestAPI = self.dynamicClient.resources.get(api_version="packages.operators.coreos.com/v1", kind="PackageManifest")
|
|
162
|
+
packagemanifestAPI.get(name="grafana-operator", namespace="openshift-marketplace")
|
|
163
|
+
self.setParam("grafana_action", "install")
|
|
164
|
+
except NotFoundError:
|
|
165
|
+
self.setParam("grafana_action", "none")
|
|
166
|
+
|
|
167
|
+
if self.interactiveMode:
|
|
168
|
+
self.printH1("Configure Grafana")
|
|
169
|
+
if self.getParam("grafana_action") == "none":
|
|
170
|
+
print_formatted_text("The Grafana operator package is not available in any catalogs on the target cluster, the installation of Grafana will be disabled")
|
|
171
|
+
else:
|
|
172
|
+
self.promptForString("Install namespace", "grafana_v5_namespace", default="grafana5")
|
|
173
|
+
self.promptForString("Grafana storage size", "grafana_instance_storage_size", default="10Gi")
|
|
174
|
+
|
|
175
|
+
def configMongoDb(self) -> None:
|
|
176
|
+
self.printH1("Configure MongoDb")
|
|
177
|
+
self.promptForString("Install namespace", "mongodb_namespace", default="mongoce")
|
|
178
|
+
|
|
179
|
+
def configCP4D(self):
|
|
180
|
+
# TODO: It's probably time to remove v8-amd64 support from the CLI entirely now
|
|
181
|
+
if self.getParam("mas_catalog_version") in ["v8-amd64", "v9-240625-amd64"]:
|
|
182
|
+
self.setParam("cpd_product_version", "4.8.0")
|
|
183
|
+
elif self.getParam("mas_catalog_version") in ["v8-240528-amd64"]:
|
|
184
|
+
self.setParam("cpd_product_version", "4.6.6")
|
|
185
|
+
else:
|
|
186
|
+
self.printDescription([
|
|
187
|
+
f"Unknown catalog {self.getParam('mas_catalog_version')}, please manually select the version of Cloud Pak for Data to use"
|
|
188
|
+
])
|
|
189
|
+
self.promptForString("Cloud Pak for Data product version", "cpd_product_version", default="4.8.0")
|
|
190
|
+
|
|
191
|
+
self.deployCP4D = True
|
|
192
|
+
|
|
193
|
+
def configMAS(self):
|
|
194
|
+
self.printH1("Configure MAS Instance")
|
|
195
|
+
self.printDescription([
|
|
196
|
+
"Instance ID restrictions:",
|
|
197
|
+
" - Must be 3-12 characters long",
|
|
198
|
+
" - Must only use lowercase letters, numbers, and hypen (-) symbol",
|
|
199
|
+
" - Must start with a lowercase letter",
|
|
200
|
+
" - Must end with a lowercase letter or a number"
|
|
201
|
+
])
|
|
202
|
+
self.promptForString("Instance ID", "mas_instance_id", validator=InstanceIDFormatValidator(), default="dev1")
|
|
203
|
+
self.printDescription([
|
|
204
|
+
"",
|
|
205
|
+
"Workspace ID restrictions:",
|
|
206
|
+
" - Must be 3-12 characters long",
|
|
207
|
+
" - Must only use lowercase letters and numbers",
|
|
208
|
+
" - Must start with a lowercase letter"
|
|
209
|
+
])
|
|
210
|
+
self.promptForString("Workspace ID", "mas_workspace_id", validator=WorkspaceIDFormatValidator(), default="ws1")
|
|
211
|
+
self.printDescription([
|
|
212
|
+
"",
|
|
213
|
+
"Workspace display name restrictions:",
|
|
214
|
+
" - Must be 3-300 characters long"
|
|
215
|
+
])
|
|
216
|
+
self.promptForString("Workspace name", "mas_workspace_name", validator=WorkspaceNameFormatValidator(), default="My Workspace")
|
|
217
|
+
|
|
218
|
+
self.configOperationMode()
|
|
219
|
+
self.configCATrust()
|
|
220
|
+
self.configDNSAndCerts()
|
|
221
|
+
|
|
222
|
+
def configCATrust(self) -> None:
|
|
223
|
+
self.printH1("Certificate Authority Trust")
|
|
224
|
+
self.printDescription([
|
|
225
|
+
"By default, Maximo Application Suite is configured to trust well-known certificate authoritories, you can disable this so that it will only trust the CAs that you explicitly define"
|
|
226
|
+
])
|
|
227
|
+
self.yesOrNo("Trust default CAs", "mas_trust_default_cas")
|
|
228
|
+
|
|
229
|
+
def configOperationMode(self):
|
|
230
|
+
self.printH1("Configure Operational Mode")
|
|
231
|
+
self.printDescription([
|
|
232
|
+
"Maximo Application Suite can be installed in a non-production mode for internal development and testing, this setting cannot be changed after installation:",
|
|
233
|
+
" - All applications, add-ons, and solutions have 0 (zero) installation AppPoints in non-production installations.",
|
|
234
|
+
" - These specifications are also visible in the metrics that are shared with IBM and in the product UI.",
|
|
235
|
+
"",
|
|
236
|
+
" 1. Production",
|
|
237
|
+
" 2. Non-Production"
|
|
238
|
+
])
|
|
239
|
+
self.operationalMode = self.promptForInt("Operational Mode", default=1)
|
|
240
|
+
|
|
241
|
+
if self.operationalMode == 2:
|
|
242
|
+
self.setParam("mas_annotations", "mas.ibm.com/operationalMode=nonproduction")
|
|
243
|
+
|
|
244
|
+
def configSNO(self):
|
|
245
|
+
if self.isSNO():
|
|
246
|
+
self.setParam("mongodb_replicas", "1")
|
|
247
|
+
self.setParam("mongodb_cpu_requests", "500m")
|
|
248
|
+
self.setParam("mas_app_settings_aio_flag", "false")
|
|
249
|
+
|
|
250
|
+
def configDNSAndCerts(self):
|
|
251
|
+
self.printH1("Cluster Ingress Secret Override")
|
|
252
|
+
self.printDescription([
|
|
253
|
+
"In most OpenShift clusters the installation is able to automatically locate the default ingress certificate, however in some configurations it is necessary to manually configure the name of the secret",
|
|
254
|
+
"Unless you see an error during the ocp-verify stage indicating that the secret can not be determined you do not need to set this and can leave the response empty"
|
|
255
|
+
])
|
|
256
|
+
self.promptForString("Cluster ingress certificate secret name", "ocp_ingress_tls_secret_name", default="")
|
|
257
|
+
|
|
258
|
+
self.printH1("Configure Domain & Certificate Management")
|
|
259
|
+
configureDomainAndCertMgmt = self.yesOrNo('Configure domain & certificate management')
|
|
260
|
+
if configureDomainAndCertMgmt:
|
|
261
|
+
configureDomain = self.yesOrNo('Configure custom domain')
|
|
262
|
+
if configureDomain:
|
|
263
|
+
self.promptForString("MAS top-level domain", "mas_domain")
|
|
264
|
+
self.printDescription([
|
|
265
|
+
"",
|
|
266
|
+
"DNS Integrations:",
|
|
267
|
+
" 1. Cloudflare",
|
|
268
|
+
" 2. IBM Cloud Internet Services",
|
|
269
|
+
" 3. AWS Route 53",
|
|
270
|
+
" 4. None (I will set up DNS myself)"
|
|
271
|
+
])
|
|
272
|
+
|
|
273
|
+
dnsProvider = self.promptForInt("DNS Provider")
|
|
274
|
+
|
|
275
|
+
if dnsProvider == 1:
|
|
276
|
+
self.configDNSAndCertsCloudflare()
|
|
277
|
+
elif dnsProvider == 2:
|
|
278
|
+
self.configDNSAndCertsCIS()
|
|
279
|
+
elif dnsProvider == 3:
|
|
280
|
+
self.configDNSAndCertsRoute53()
|
|
281
|
+
elif dnsProvider == 4:
|
|
282
|
+
# Use MAS default self-signed cluster issuer with a custom domain
|
|
283
|
+
self.setParam("dns_provider", "")
|
|
284
|
+
self.setParam("mas_cluster_issuer", "")
|
|
285
|
+
else:
|
|
286
|
+
# Use MAS default self-signed cluster issuer with the default domain
|
|
287
|
+
self.setParam("dns_provider", "")
|
|
288
|
+
self.setParam("mas_domain", "")
|
|
289
|
+
self.setParam("mas_cluster_issuer", "")
|
|
290
|
+
|
|
291
|
+
def configDNSAndCertsCloudflare(self):
|
|
292
|
+
# User has chosen to set up DNS integration with Cloudflare
|
|
293
|
+
self.setParam("dns_provider", "cloudflare")
|
|
294
|
+
self.promptForString("Cloudflare e-mail", "cloudflare_email")
|
|
295
|
+
self.promptForString("Cloudflare API token", "cloudflare_apitoken")
|
|
296
|
+
self.promptForString("Cloudflare zone", "cloudflare_zone")
|
|
297
|
+
self.promptForString("Cloudflare subdomain", "cloudflare_subdomain")
|
|
298
|
+
|
|
299
|
+
self.printDescription([
|
|
300
|
+
"Certificate Issuer:",
|
|
301
|
+
" 1. LetsEncrypt (Production)",
|
|
302
|
+
" 2. LetsEncrypt (Staging)",
|
|
303
|
+
" 3. Self-Signed"
|
|
304
|
+
])
|
|
305
|
+
certIssuer = self.promptForInt("Certificate issuer")
|
|
306
|
+
certIssuerOptions = [
|
|
307
|
+
f"${self.getParam('mas_instance_id')}-cloudflare-le-prod",
|
|
308
|
+
f"${self.getParam('mas_instance_id')}-cloudflare-le-stg",
|
|
309
|
+
""
|
|
310
|
+
]
|
|
311
|
+
self.setParam("mas_cluster_issuer", certIssuerOptions[certIssuer-1])
|
|
312
|
+
|
|
313
|
+
def configDNSAndCertsCIS(self):
|
|
314
|
+
self.setParam("dns_provider", "cis")
|
|
315
|
+
self.promptForString("CIS e-mail", "cis_email")
|
|
316
|
+
self.promptForString("CIS API token", "cis_apikey")
|
|
317
|
+
self.promptForString("CIS CRN", "cis_crn")
|
|
318
|
+
self.promptForString("CIS subdomain", "cis_subdomain")
|
|
319
|
+
|
|
320
|
+
self.printDescription([
|
|
321
|
+
"Certificate Issuer:",
|
|
322
|
+
" 1. LetsEncrypt (Production)",
|
|
323
|
+
" 2. LetsEncrypt (Staging)",
|
|
324
|
+
" 3. Self-Signed"
|
|
325
|
+
])
|
|
326
|
+
certIssuer = self.promptForInt("Certificate issuer")
|
|
327
|
+
certIssuerOptions = [
|
|
328
|
+
f"${self.getParam('mas_instance_id')}-cis-le-prod",
|
|
329
|
+
f"${self.getParam('mas_instance_id')}-cis-le-stg",
|
|
330
|
+
""
|
|
331
|
+
]
|
|
332
|
+
self.setParam("mas_cluster_issuer", certIssuerOptions[certIssuer-1])
|
|
333
|
+
|
|
334
|
+
def configDNSAndCertsRoute53(self):
|
|
335
|
+
self.setParam("dns_provider", "route53")
|
|
336
|
+
self.printDescription([
|
|
337
|
+
"Provide your AWS account access key ID & secret access key",
|
|
338
|
+
"This will be used to authenticate into the AWS account where your AWS Route 53 hosted zone instance is located",
|
|
339
|
+
""
|
|
340
|
+
])
|
|
341
|
+
self.promptForString("AWS Access Key ID", "aws_access_key_id", isPassword=True)
|
|
342
|
+
self.promptForString("AWS Secret Access Key", "aws_secret_access_key", isPassword=True)
|
|
343
|
+
|
|
344
|
+
self.printDescription([
|
|
345
|
+
"Provide your AWS Route 53 hosted zone instance details",
|
|
346
|
+
"This information will be used to create webhook resources between your cluster and your AWS Route 53 instance (cluster issuer and cname records)",
|
|
347
|
+
"in order for it to be able to resolve DNS entries for all the subdomains created for your Maximo Application Suite instance",
|
|
348
|
+
"",
|
|
349
|
+
"Therefore, the AWS Route 53 subdomain + the AWS Route 53 hosted zone name defined, when combined, needs to match with the chosen MAS Top Level domain, otherwise the DNS records won't be able to get resolved"
|
|
350
|
+
])
|
|
351
|
+
self.promptForString("AWS Route 53 hosted zone name", "route53_hosted_zone_name")
|
|
352
|
+
self.promptForString("AWS Route 53 hosted zone region", "route53_hosted_zone_region")
|
|
353
|
+
self.promptForString("AWS Route 53 subdomain", "route53_subdomain")
|
|
354
|
+
self.promptForString("AWS Route 53 e-mail", "route53_email")
|
|
355
|
+
|
|
356
|
+
self.setParam("mas_cluster_issuer", f"{self.getParam('mas_instance_id')}-route53-le-prod")
|
|
357
|
+
|
|
358
|
+
def configApps(self):
|
|
359
|
+
self.printH1("Application Selection")
|
|
360
|
+
self.installIoT = self.yesOrNo("Install IoT")
|
|
361
|
+
|
|
362
|
+
if self.installIoT:
|
|
363
|
+
self.configAppChannel("iot")
|
|
364
|
+
self.installMonitor = self.yesOrNo("Install Monitor")
|
|
365
|
+
else:
|
|
366
|
+
self.installMonitor = False
|
|
367
|
+
|
|
368
|
+
if self.installMonitor:
|
|
369
|
+
self.configAppChannel("monitor")
|
|
370
|
+
|
|
371
|
+
self.installManage = self.yesOrNo("Install Manage")
|
|
372
|
+
|
|
373
|
+
if self.installManage:
|
|
374
|
+
self.configAppChannel("manage")
|
|
375
|
+
|
|
376
|
+
# Predict for MAS 8.10 is effectively unsupported now, because it has not shipped support for Cloud Pak for Data 4.8 as of June 2023 catalog update
|
|
377
|
+
if self.installIoT and self.installManage and self.getParam("mas_channel") != "8.10.x":
|
|
378
|
+
self.installPredict = self.yesOrNo("Install Predict")
|
|
379
|
+
else:
|
|
380
|
+
self.installPredict = False
|
|
381
|
+
|
|
382
|
+
if self.installPredict:
|
|
383
|
+
self.configAppChannel("predict")
|
|
384
|
+
|
|
385
|
+
# Assist is only installable on MAS 9.0.x due to withdrawal of support for Watson Discovery in our managed dependency stack and the inability of Assist 8.x to support this
|
|
386
|
+
if not self.getParam("mas_channel").startswith("8."):
|
|
387
|
+
self.installAssist = self.yesOrNo("Install Assist")
|
|
388
|
+
if self.installAssist:
|
|
389
|
+
self.configAppChannel("assist")
|
|
390
|
+
|
|
391
|
+
self.installOptimizer = self.yesOrNo("Install Optimizer")
|
|
392
|
+
if self.installOptimizer:
|
|
393
|
+
self.configAppChannel("optimizer")
|
|
394
|
+
|
|
395
|
+
self.installInspection = self.yesOrNo("Install Visual Inspection")
|
|
396
|
+
if self.installInspection:
|
|
397
|
+
self.configAppChannel("visualinspection")
|
|
398
|
+
|
|
399
|
+
def configAppChannel(self, appId):
|
|
400
|
+
versions = self.getCompatibleVersions(self.params["mas_channel"], appId)
|
|
401
|
+
if len(versions) == 0:
|
|
402
|
+
self.params[f"mas_app_channel_{appId}"] = prompt(HTML('<Yellow>Custom channel</Yellow> '))
|
|
403
|
+
else:
|
|
404
|
+
self.params[f"mas_app_channel_{appId}"] = versions[0]
|
|
405
|
+
|
|
406
|
+
def configStorageClasses(self):
|
|
407
|
+
self.printH1("Configure Storage Class Usage")
|
|
408
|
+
self.printDescription([
|
|
409
|
+
"Maximo Application Suite and it's dependencies require storage classes that support ReadWriteOnce (RWO) and ReadWriteMany (RWX) access modes:",
|
|
410
|
+
" - ReadWriteOnce volumes can be mounted as read-write by multiple pods on a single node.",
|
|
411
|
+
" - ReadWriteMany volumes can be mounted as read-write by multiple pods across many nodes.",
|
|
412
|
+
""
|
|
413
|
+
])
|
|
414
|
+
# 1. ROKS
|
|
415
|
+
if getStorageClass(self.dynamicClient, "ibmc-file-gold-gid") is not None:
|
|
416
|
+
print_formatted_text(HTML("<MediumSeaGreen>Storage provider auto-detected: IBMCloud ROKS</MediumSeaGreen>"))
|
|
417
|
+
print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteOnce): ibmc-block-gold</LightSlateGrey>"))
|
|
418
|
+
print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteMany): ibmc-file-gold-gid</LightSlateGrey>"))
|
|
419
|
+
self.storageClassProvider = "ibmc"
|
|
420
|
+
self.params["storage_class_rwo"] = "ibmc-block-gold"
|
|
421
|
+
self.params["storage_class_rwx"] = "ibmc-file-gold-gid"
|
|
422
|
+
# 2. OCS
|
|
423
|
+
elif getStorageClass(self.dynamicClient, "ocs-storagecluster-cephfs") is not None:
|
|
424
|
+
print_formatted_text(HTML("<MediumSeaGreen>Storage provider auto-detected: OpenShift Container Storage</MediumSeaGreen>"))
|
|
425
|
+
print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteOnce): ocs-storagecluster-ceph-rbd</LightSlateGrey>"))
|
|
426
|
+
print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteMany): ocs-storagecluster-cephfs</LightSlateGrey>"))
|
|
427
|
+
self.storageClassProvider = "ocs"
|
|
428
|
+
self.params["storage_class_rwo"] = "ocs-storagecluster-ceph-rbd"
|
|
429
|
+
self.params["storage_class_rwx"] = "ocs-storagecluster-cephfs"
|
|
430
|
+
# 3. Azure
|
|
431
|
+
elif getStorageClass(self.dynamicClient, "managed-premium") is not None:
|
|
432
|
+
print_formatted_text(HTML("<MediumSeaGreen>Storage provider auto-detected: Azure Managed</MediumSeaGreen>"))
|
|
433
|
+
print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteOnce): azurefiles-premium</LightSlateGrey>"))
|
|
434
|
+
print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteMany): managed-premium</LightSlateGrey>"))
|
|
435
|
+
self.storageClassProvider = "azure"
|
|
436
|
+
self.params["storage_class_rwo"] = "azurefiles-premium"
|
|
437
|
+
self.params["storage_class_rwx"] = "managed-premium"
|
|
438
|
+
# 4. AWS
|
|
439
|
+
elif getStorageClass(self.dynamicClient, "gp2") is not None:
|
|
440
|
+
print_formatted_text(HTML("<MediumSeaGreen>Storage provider auto-detected: AWS gp2/MediumSeaGreen>"))
|
|
441
|
+
print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteOnce): gp2</LightSlateGrey>"))
|
|
442
|
+
print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteMany): efs</LightSlateGrey>"))
|
|
443
|
+
self.storageClassProvider = "aws"
|
|
444
|
+
self.params["storage_class_rwo"] = "gp2"
|
|
445
|
+
self.params["storage_class_rwx"] = "efs"
|
|
446
|
+
|
|
447
|
+
overrideStorageClasses = False
|
|
448
|
+
if "storage_class_rwx" in self.params and self.params["storage_class_rwx"] != "":
|
|
449
|
+
overrideStorageClasses = not self.yesOrNo("Use the auto-detected storage classes")
|
|
450
|
+
|
|
451
|
+
if "storage_class_rwx" not in self.params or self.params["storage_class_rwx"] == "" or overrideStorageClasses:
|
|
452
|
+
self.storageClassProvider = "custom"
|
|
453
|
+
|
|
454
|
+
self.printDescription([
|
|
455
|
+
"Select the ReadWriteOnce and ReadWriteMany storage classes to use from the list below:",
|
|
456
|
+
"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"
|
|
457
|
+
])
|
|
458
|
+
for storageClass in getStorageClasses(self.dynamicClient):
|
|
459
|
+
print_formatted_text(HTML(f"<LightSlateGrey> - {storageClass.metadata.name}</LightSlateGrey>"))
|
|
460
|
+
|
|
461
|
+
self.params["storage_class_rwo"] = prompt(HTML('<Yellow>ReadWriteOnce (RWO) storage class</Yellow> '), validator=StorageClassValidator(), validate_while_typing=False)
|
|
462
|
+
self.params["storage_class_rwx"] = prompt(HTML('<Yellow>ReadWriteMany (RWX) storage class</Yellow> '), validator=StorageClassValidator(), validate_while_typing=False)
|
|
463
|
+
|
|
464
|
+
# Configure storage class for pipeline PVC
|
|
465
|
+
# We prefer to use ReadWriteMany, but we can cope with ReadWriteOnce if necessary
|
|
466
|
+
if self.isSNO() or self.params["storage_class_rwx"] == "none":
|
|
467
|
+
self.pipelineStorageClass = self.getParam("storage_class_rwo")
|
|
468
|
+
self.pipelineStorageAccessMode = "ReadWriteOnce"
|
|
469
|
+
else:
|
|
470
|
+
self.pipelineStorageClass = self.getParam("storage_class_rwx")
|
|
471
|
+
self.pipelineStorageAccessMode = "ReadWriteMany"
|
|
472
|
+
|
|
473
|
+
def setIoTStorageClasses(self) -> None:
|
|
474
|
+
if self.installIoT:
|
|
475
|
+
self.setParam("mas_app_settings_iot_fpl_pvc_storage_class", self.getParam("storage_class_rwo"))
|
|
476
|
+
self.setParam("mas_app_settings_iot_mqttbroker_pvc_storage_class", self.getParam("storage_class_rwo"))
|
|
477
|
+
|
|
478
|
+
def optimizerSettings(self) -> None:
|
|
479
|
+
if self.installOptimizer:
|
|
480
|
+
self.printH1("Configure Maximo Optimizer")
|
|
481
|
+
self.printDescription(["Customize your Optimizer installation, 'full' and 'limited' install plans are available, refer to the product documentation for more information"])
|
|
482
|
+
|
|
483
|
+
self.promptForString("Plan [full/limited]", "mas_app_plan_optimizer", default="full", validator=OptimizerInstallPlanValidator())
|
|
484
|
+
|
|
485
|
+
def predictSettings(self) -> None:
|
|
486
|
+
if self.installPredict:
|
|
487
|
+
self.printH1("Configure Maximo Predict")
|
|
488
|
+
self.printDescription([
|
|
489
|
+
"Predict application supports integration with IBM SPSS and Watson Openscale which are optional services installed on top of IBM Cloud Pak for Data",
|
|
490
|
+
"Unless requested these will not be installed"
|
|
491
|
+
])
|
|
492
|
+
self.configCP4D()
|
|
493
|
+
self.yesOrNo("Install IBM SPSS Statistics", "cpd_install_spss")
|
|
494
|
+
self.yesOrNo("Install Watson OpenScale", "cpd_install_openscale")
|
|
495
|
+
|
|
496
|
+
def assistSettings(self) -> None:
|
|
497
|
+
if self.installAssist:
|
|
498
|
+
self.printH1("Configure Maximo Assist")
|
|
499
|
+
self.printDescription([
|
|
500
|
+
"Assist requires access to Cloud Object Storage (COS), this install supports automatic setup using either IBMCloud COS or in-cluster COS via OpenShift Container Storage/OpenShift Data Foundation (OCS/ODF)"
|
|
501
|
+
])
|
|
502
|
+
self.configCP4D()
|
|
503
|
+
self.promptForString("COS Provider [ibm/ocs]", "cos_type")
|
|
504
|
+
if self.getParam("cos_type") == "ibm":
|
|
505
|
+
self.promptForString("IBM Cloud API Key", "ibmcloud_apikey", isPassword=True)
|
|
506
|
+
self.promptForString("IBM Cloud Resource Group", "ibmcos_resourcegroup")
|
|
507
|
+
|
|
508
|
+
def interactiveMode(self) -> None:
|
|
509
|
+
# Interactive mode
|
|
510
|
+
self.interactiveMode = True
|
|
511
|
+
|
|
512
|
+
# Catalog
|
|
513
|
+
self.configCatalog()
|
|
514
|
+
self.validateCatalogSource()
|
|
515
|
+
self.licensePrompt()
|
|
516
|
+
|
|
517
|
+
# SNO & Storage Classes
|
|
518
|
+
self.configSNO()
|
|
519
|
+
self.configStorageClasses()
|
|
520
|
+
|
|
521
|
+
# Licensing (SLS and DRO)
|
|
522
|
+
self.configSLS()
|
|
523
|
+
self.configICRCredentials()
|
|
524
|
+
|
|
525
|
+
# MAS Core
|
|
526
|
+
self.configCertManager()
|
|
527
|
+
self.configMAS()
|
|
528
|
+
|
|
529
|
+
# MAS Applications
|
|
530
|
+
self.configApps()
|
|
531
|
+
self.validateInternalRegistryAvailable()
|
|
532
|
+
# Note: manageSettings(), predictSettings(), or assistSettings() functions can trigger configCP4D()
|
|
533
|
+
self.manageSettings()
|
|
534
|
+
self.optimizerSettings()
|
|
535
|
+
self.predictSettings()
|
|
536
|
+
self.assistSettings()
|
|
537
|
+
|
|
538
|
+
# Dependencies
|
|
539
|
+
self.configMongoDb() # Will only do anything if IoT or Manage have been selected for install
|
|
540
|
+
self.configDb2()
|
|
541
|
+
self.configKafka() # Will only do anything if IoT has been selected for install
|
|
542
|
+
|
|
543
|
+
self.configGrafana()
|
|
544
|
+
self.configTurbonomic()
|
|
545
|
+
|
|
546
|
+
# TODO: Support ECK integration via the interactive install mode
|
|
547
|
+
# TODO: Support MAS superuser username/password via the interactive install mode
|
|
548
|
+
|
|
549
|
+
def nonInteractiveMode(self) -> None:
|
|
550
|
+
# Non-interactive mode
|
|
551
|
+
self.interactiveMode = False
|
|
552
|
+
|
|
553
|
+
# Set defaults
|
|
554
|
+
self.storageClassProvider="custom"
|
|
555
|
+
self.installAssist = False
|
|
556
|
+
self.installIoT = False
|
|
557
|
+
self.installMonitor = False
|
|
558
|
+
self.installManage = False
|
|
559
|
+
self.installPredict = False
|
|
560
|
+
self.installInspection = False
|
|
561
|
+
self.installOptimizer = False
|
|
562
|
+
self.deployCP4D = False
|
|
563
|
+
self.db2SetAffinity = False
|
|
564
|
+
self.db2SetTolerations = False
|
|
565
|
+
|
|
566
|
+
self.configGrafana()
|
|
567
|
+
|
|
568
|
+
requiredParams = [
|
|
569
|
+
"mas_catalog_version",
|
|
570
|
+
"mas_channel",
|
|
571
|
+
"mas_instance_id",
|
|
572
|
+
"mas_workspace_id",
|
|
573
|
+
"mas_workspace_name",
|
|
574
|
+
"storage_class_rwo",
|
|
575
|
+
"storage_class_rwx",
|
|
576
|
+
"ibm_entitlement_key",
|
|
577
|
+
"uds_contact_email",
|
|
578
|
+
"uds_contact_firstname",
|
|
579
|
+
"uds_contact_lastname"
|
|
580
|
+
]
|
|
581
|
+
optionalParams = [
|
|
582
|
+
"mas_superuser_username",
|
|
583
|
+
"mas_superuser_password",
|
|
584
|
+
"mas_trust_default_cas",
|
|
585
|
+
"mas_app_settings_server_bundles_size",
|
|
586
|
+
"mas_app_settings_default_jms",
|
|
587
|
+
"mas_app_settings_persistent_volumes_flag",
|
|
588
|
+
"mas_appws_bindings_jdbc_manage",
|
|
589
|
+
"mas_app_settings_demodata",
|
|
590
|
+
"mas_appws_components",
|
|
591
|
+
"mas_app_settings_customization_archive_name",
|
|
592
|
+
"mas_app_settings_customization_archive_url",
|
|
593
|
+
"mas_app_settings_customization_archive_username",
|
|
594
|
+
"mas_app_settings_customization_archive_password",
|
|
595
|
+
"mas_app_settings_tablespace",
|
|
596
|
+
"mas_app_settings_indexspace",
|
|
597
|
+
"mas_app_settings_db2_schema",
|
|
598
|
+
"mas_app_settings_crypto_key",
|
|
599
|
+
"mas_app_settings_cryptox_key",
|
|
600
|
+
"mas_app_settings_old_crypto_key",
|
|
601
|
+
"mas_app_settings_old_cryptox_key",
|
|
602
|
+
"mas_app_settings_override_encryption_secrets_flag",
|
|
603
|
+
"mas_app_settings_base_lang",
|
|
604
|
+
"mas_app_settings_secondary_langs",
|
|
605
|
+
"mas_app_settings_server_timezone",
|
|
606
|
+
"ocp_ingress_tls_secret_name",
|
|
607
|
+
"dro_namespace",
|
|
608
|
+
"mongodb_namespace",
|
|
609
|
+
"cpd_product_version",
|
|
610
|
+
"db2_action_system",
|
|
611
|
+
"db2_action_manage",
|
|
612
|
+
"db2_type",
|
|
613
|
+
"db2_timezone",
|
|
614
|
+
"db2_namespace",
|
|
615
|
+
"db2_channel",
|
|
616
|
+
"db2_affinity_key",
|
|
617
|
+
"db2_affinity_value",
|
|
618
|
+
"db2_tolerate_key",
|
|
619
|
+
"db2_tolerate_value",
|
|
620
|
+
"db2_tolerate_effect",
|
|
621
|
+
"db2_cpu_requests",
|
|
622
|
+
"db2_cpu_limits",
|
|
623
|
+
"db2_memory_requests",
|
|
624
|
+
"db2_memory_limits",
|
|
625
|
+
"db2_backup_storage_size",
|
|
626
|
+
"db2_data_storage_size",
|
|
627
|
+
"db2_logs_storage_size",
|
|
628
|
+
"db2_meta_storage_size",
|
|
629
|
+
"db2_temp_storage_size",
|
|
630
|
+
"cpd_install_cognos",
|
|
631
|
+
"cpd_install_openscale",
|
|
632
|
+
"cpd_install_spss",
|
|
633
|
+
"kafka_namespace",
|
|
634
|
+
"kafka_version",
|
|
635
|
+
"aws_msk_instance_type",
|
|
636
|
+
"aws_msk_instance_number",
|
|
637
|
+
"aws_msk_volume_size",
|
|
638
|
+
"aws_msk_cidr_az1",
|
|
639
|
+
"aws_msk_cidr_az2",
|
|
640
|
+
"aws_msk_cidr_az3",
|
|
641
|
+
"aws_msk_egress_cidr",
|
|
642
|
+
"aws_msk_ingress_cidr",
|
|
643
|
+
"eventstreams_resource_group",
|
|
644
|
+
"eventstreams_instance_name",
|
|
645
|
+
"eventstreams_instance_location",
|
|
646
|
+
"eck_action",
|
|
647
|
+
"eck_enable_logstash",
|
|
648
|
+
"eck_remote_es_hosts",
|
|
649
|
+
"eck_remote_es_username",
|
|
650
|
+
"eck_remote_es_password",
|
|
651
|
+
"turbonomic_target_name",
|
|
652
|
+
"turbonomic_server_url",
|
|
653
|
+
"turbonomic_server_version",
|
|
654
|
+
"turbonomic_username",
|
|
655
|
+
"turbonomic_password",
|
|
656
|
+
"ibmcloud_apikey",
|
|
657
|
+
"aws_region",
|
|
658
|
+
"aws_access_key_id",
|
|
659
|
+
"secret_access_key",
|
|
660
|
+
"aws_vpc_id",
|
|
661
|
+
# TODO: The way arcgis has been implemented needs to be fixed
|
|
662
|
+
"install_arcgis",
|
|
663
|
+
"mas_arcgis_channel"
|
|
664
|
+
]
|
|
665
|
+
|
|
666
|
+
for key, value in vars(args).items():
|
|
667
|
+
# These fields we just pass straight through to the parameters and fail if they are not set
|
|
668
|
+
if key in requiredParams:
|
|
669
|
+
if value is None:
|
|
670
|
+
self.fatalError(f"{key} must be set")
|
|
671
|
+
self.setParam(key, value)
|
|
672
|
+
|
|
673
|
+
# These fields we just pass straight through to the parameters
|
|
674
|
+
elif key in optionalParams:
|
|
675
|
+
if value is not None:
|
|
676
|
+
self.setParam(key, value)
|
|
677
|
+
|
|
678
|
+
elif key == "kafka_provider":
|
|
679
|
+
if value is not None:
|
|
680
|
+
self.setParam("kafka_provider", value)
|
|
681
|
+
self.setParam("kafka_action_system", "install")
|
|
682
|
+
|
|
683
|
+
elif key == "kafka_username":
|
|
684
|
+
if value is not None:
|
|
685
|
+
self.setParam("kafka_user_name", value)
|
|
686
|
+
self.setParam("aws_kafka_user_name", value)
|
|
687
|
+
|
|
688
|
+
elif key == "kafka_password":
|
|
689
|
+
if value is not None:
|
|
690
|
+
self.setParam("kafka_user_password", value)
|
|
691
|
+
self.setParam("aws_kafka_user_password", value)
|
|
692
|
+
|
|
693
|
+
elif key == "non_prod":
|
|
694
|
+
if not value:
|
|
695
|
+
self.operationalMode = 1
|
|
696
|
+
else:
|
|
697
|
+
self.operationalMode = 2
|
|
698
|
+
|
|
699
|
+
elif key == "additional_configs":
|
|
700
|
+
self.localConfigDir = value
|
|
701
|
+
|
|
702
|
+
elif key == "pod_templates":
|
|
703
|
+
# For the named configurations we will convert into the path
|
|
704
|
+
if value in ["best-effort", "guaranteed"]:
|
|
705
|
+
self.setParam("mas_pod_templates_dir", path.join(self.templatesDir, "pod-templates", value))
|
|
706
|
+
else:
|
|
707
|
+
self.setParam("mas_pod_templates_dir", value)
|
|
708
|
+
|
|
709
|
+
elif key == "assist_channel":
|
|
710
|
+
if value is not None:
|
|
711
|
+
self.setParam("mas_app_channel_assist", value)
|
|
712
|
+
self.installAssist = True
|
|
713
|
+
elif key == "iot_channel":
|
|
714
|
+
if value is not None:
|
|
715
|
+
self.setParam("mas_app_channel_iot", value)
|
|
716
|
+
self.installIoT = True
|
|
717
|
+
elif key == "monitor_channel":
|
|
718
|
+
if value is not None:
|
|
719
|
+
self.setParam("mas_app_channel_monitor", value)
|
|
720
|
+
self.installMonitor = True
|
|
721
|
+
elif key == "manage_channel":
|
|
722
|
+
if value is not None:
|
|
723
|
+
self.setParam("mas_app_channel_manage", value)
|
|
724
|
+
self.installManage = True
|
|
725
|
+
elif key == "predict_channel":
|
|
726
|
+
if value is not None:
|
|
727
|
+
self.setParam("mas_app_channel_predict", value)
|
|
728
|
+
self.installPredict = True
|
|
729
|
+
self.deployCP4D = True
|
|
730
|
+
elif key == "visualinspection_channel":
|
|
731
|
+
if value is not None:
|
|
732
|
+
self.setParam("mas_app_channel_visualinspection", value)
|
|
733
|
+
self.installInspection = True
|
|
734
|
+
elif key == "optimizer_channel":
|
|
735
|
+
if value is not None:
|
|
736
|
+
self.setParam("mas_app_channel_optimizer", value)
|
|
737
|
+
self.installOptimizer = True
|
|
738
|
+
elif key == "optimizer_plan":
|
|
739
|
+
if value is not None:
|
|
740
|
+
self.setParam("mas_app_plan_optimizer", value)
|
|
741
|
+
|
|
742
|
+
# Manage advanced settings that need extra processing
|
|
743
|
+
elif key == "mas_app_settings_server_bundle_size":
|
|
744
|
+
if value is not None:
|
|
745
|
+
self.setParam(key, value)
|
|
746
|
+
if value in ["jms", "snojms"]:
|
|
747
|
+
self.setParam("mas_app_settings_persistent_volumes_flag", "true")
|
|
748
|
+
|
|
749
|
+
# These settings are used by the CLI rather than passed to the PipelineRun
|
|
750
|
+
elif key == "storage_accessmode":
|
|
751
|
+
if value is None:
|
|
752
|
+
self.fatalError(f"{key} must be set")
|
|
753
|
+
self.pipelineStorageAccessMode = value
|
|
754
|
+
elif key == "storage_pipeline":
|
|
755
|
+
if value is None:
|
|
756
|
+
self.fatalError(f"{key} must be set")
|
|
757
|
+
self.pipelineStorageClass = value
|
|
758
|
+
elif key == "license_file":
|
|
759
|
+
if value is None:
|
|
760
|
+
self.fatalError(f"{key} must be set")
|
|
761
|
+
self.slsLicenseFileLocal = value
|
|
762
|
+
|
|
763
|
+
# Arguments that we don't need to do anything with
|
|
764
|
+
elif key in ["accept_license", "dev_mode", "skip_pre_check", "no_confirm", "no_wait_for_pvc", "help"]:
|
|
765
|
+
pass
|
|
766
|
+
|
|
767
|
+
# Fail if there's any arguments we don't know how to handle
|
|
768
|
+
else:
|
|
769
|
+
print(f"Unknown option: {key} {value}")
|
|
770
|
+
self.fatalError(f"Unknown option: {key} {value}")
|
|
771
|
+
|
|
772
|
+
# Once we've processed the inputs, we should validate the catalog source & prompt to accept the license terms
|
|
773
|
+
self.validateCatalogSource()
|
|
774
|
+
self.licensePrompt()
|
|
775
|
+
|
|
776
|
+
def install(self, args):
|
|
777
|
+
"""
|
|
778
|
+
Install MAS instance
|
|
779
|
+
"""
|
|
780
|
+
instanceId = args.mas_instance_id
|
|
781
|
+
self.noConfirm = args.no_confirm
|
|
782
|
+
self.waitForPVC = not args.no_wait_for_pvc
|
|
783
|
+
self.licenseAccepted = args.accept_license
|
|
784
|
+
|
|
785
|
+
self.args = args
|
|
786
|
+
self.params = dict()
|
|
787
|
+
|
|
788
|
+
# These flags work for setting params in both interactive and non-interactive modes
|
|
789
|
+
if args.skip_pre_check:
|
|
790
|
+
self.setParam("skip_pre_check", "true")
|
|
791
|
+
|
|
792
|
+
self.installOptions = [
|
|
793
|
+
{
|
|
794
|
+
"#": 1,
|
|
795
|
+
"catalog": "v9-240625-amd64",
|
|
796
|
+
"release": "9.0.x",
|
|
797
|
+
"core": "9.0.0",
|
|
798
|
+
"assist": "9.0.0",
|
|
799
|
+
"iot": "9.0.0",
|
|
800
|
+
"manage": "9.0.0",
|
|
801
|
+
"monitor": "9.0.0",
|
|
802
|
+
"optimizer": "9.0.0",
|
|
803
|
+
"predict": "9.0.0",
|
|
804
|
+
"inspection": "9.0.0"
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
"#": 2,
|
|
808
|
+
"catalog": "v9-240625-amd64",
|
|
809
|
+
"release": "8.11.x",
|
|
810
|
+
"core": "8.11.12",
|
|
811
|
+
"assist": "8.8.4",
|
|
812
|
+
"iot": "8.8.10",
|
|
813
|
+
"manage": "8.7.9",
|
|
814
|
+
"monitor": "8.11.8",
|
|
815
|
+
"optimizer": "8.5.6",
|
|
816
|
+
"predict": "8.9.3",
|
|
817
|
+
"inspection": "8.9.3"
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
"#": 3,
|
|
821
|
+
"catalog": "v9-240625-amd64",
|
|
822
|
+
"release": "8.10.x",
|
|
823
|
+
"core": "8.10.15",
|
|
824
|
+
"assist": "8.7.5",
|
|
825
|
+
"iot": "8.7.14",
|
|
826
|
+
"manage": "8.6.15",
|
|
827
|
+
"monitor": "8.10.11",
|
|
828
|
+
"optimizer": "8.4.7",
|
|
829
|
+
"predict": "8.8.2",
|
|
830
|
+
"inspection": "8.8.4"
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
"#": 4,
|
|
834
|
+
"catalog": "v8-240528-amd64",
|
|
835
|
+
"release": "8.11.x",
|
|
836
|
+
"core": "8.11.11",
|
|
837
|
+
"assist": "8.8.3",
|
|
838
|
+
"iot": "8.8.9",
|
|
839
|
+
"manage": "8.7.8",
|
|
840
|
+
"monitor": "8.11.7",
|
|
841
|
+
"optimizer": "8.5.5",
|
|
842
|
+
"predict": "8.9.2",
|
|
843
|
+
"inspection": "8.9.3"
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
"#": 5,
|
|
847
|
+
"catalog": "v8-240528-amd64",
|
|
848
|
+
"release": "8.10.x",
|
|
849
|
+
"core": "8.10.14",
|
|
850
|
+
"assist": "8.7.4",
|
|
851
|
+
"iot": "8.7.13",
|
|
852
|
+
"manage": "8.6.14",
|
|
853
|
+
"monitor": "8.10.10",
|
|
854
|
+
"optimizer": "8.4.6",
|
|
855
|
+
"predict": "8.8.2",
|
|
856
|
+
"inspection": "8.8.4"
|
|
857
|
+
}
|
|
858
|
+
]
|
|
859
|
+
|
|
860
|
+
if instanceId is None:
|
|
861
|
+
self.printH1("Set Target OpenShift Cluster")
|
|
862
|
+
# Connect to the target cluster
|
|
863
|
+
self.connect()
|
|
864
|
+
else:
|
|
865
|
+
logger.debug("MAS instance ID is set, so we assume already connected to the desired OCP")
|
|
866
|
+
|
|
867
|
+
if self.dynamicClient is None:
|
|
868
|
+
print_formatted_text(HTML("<Red>Error: The Kubernetes dynamic Client is not available. See log file for details</Red>"))
|
|
869
|
+
exit(1)
|
|
870
|
+
|
|
871
|
+
# Basic settings before the user provides any input
|
|
872
|
+
self.configICR()
|
|
873
|
+
self.configCertManager()
|
|
874
|
+
self.deployCP4D = False
|
|
875
|
+
|
|
876
|
+
# UDS install has not been supported since the January 2024 catalog update
|
|
877
|
+
self.setParam("uds_action", "install-dro")
|
|
878
|
+
|
|
879
|
+
# User must either provide the configuration via numerous command line arguments, or the interactive prompts
|
|
880
|
+
if instanceId is None:
|
|
881
|
+
self.interactiveMode()
|
|
882
|
+
else:
|
|
883
|
+
self.nonInteractiveMode()
|
|
884
|
+
|
|
885
|
+
# After we've configured the basic inputs, we can calculate these ones
|
|
886
|
+
self.setIoTStorageClasses()
|
|
887
|
+
if self.deployCP4D:
|
|
888
|
+
self.configCP4D()
|
|
889
|
+
|
|
890
|
+
# The entitlement file for SLS is mounted as a secret in /workspace/entitlement
|
|
891
|
+
entitlementFileBaseName = path.basename(self.slsLicenseFileLocal)
|
|
892
|
+
self.setParam("sls_entitlement_file", f"/workspace/entitlement/{entitlementFileBaseName}")
|
|
893
|
+
|
|
894
|
+
# Set up the secrets for additional configs and podtemplates
|
|
895
|
+
self.additionalConfigs()
|
|
896
|
+
self.podTemplates()
|
|
897
|
+
|
|
898
|
+
# Show a summary of the installation configuration
|
|
899
|
+
self.displayInstallSummary()
|
|
900
|
+
|
|
901
|
+
if not self.noConfirm:
|
|
902
|
+
print()
|
|
903
|
+
self.printDescription([
|
|
904
|
+
"Please carefully review your choices above, correcting mistakes now is much easier than after the install has begun"
|
|
905
|
+
])
|
|
906
|
+
continueWithInstall = self.yesOrNo("Proceed with these settings")
|
|
907
|
+
|
|
908
|
+
# Prepare the namespace and launch the installation pipeline
|
|
909
|
+
if self.noConfirm or continueWithInstall:
|
|
910
|
+
self.printH1("Launch Install")
|
|
911
|
+
pipelinesNamespace = f"mas-{self.getParam('mas_instance_id')}-pipelines"
|
|
912
|
+
|
|
913
|
+
if not self.noConfirm:
|
|
914
|
+
self.printDescription(["If you are using storage classes that utilize 'WaitForFirstConsumer' binding mode choose 'No' at the prompt below"])
|
|
915
|
+
wait = self.yesOrNo("Wait for PVCs to bind")
|
|
916
|
+
else:
|
|
917
|
+
wait = False
|
|
918
|
+
|
|
919
|
+
with Halo(text='Validating OpenShift Pipelines installation', spinner=self.spinner) as h:
|
|
920
|
+
installOpenShiftPipelines(self.dynamicClient)
|
|
921
|
+
h.stop_and_persist(symbol=self.successIcon, text=f"OpenShift Pipelines Operator is installed and ready to use")
|
|
922
|
+
|
|
923
|
+
with Halo(text=f'Preparing namespace ({pipelinesNamespace})', spinner=self.spinner) as h:
|
|
924
|
+
createNamespace(self.dynamicClient, pipelinesNamespace)
|
|
925
|
+
preparePipelinesNamespace(
|
|
926
|
+
dynClient=self.dynamicClient,
|
|
927
|
+
instanceId=self.getParam("mas_instance_id"),
|
|
928
|
+
storageClass=self.pipelineStorageClass,
|
|
929
|
+
accessMode=self.pipelineStorageAccessMode,
|
|
930
|
+
waitForBind=wait
|
|
931
|
+
)
|
|
932
|
+
prepareInstallSecrets(
|
|
933
|
+
dynClient=self.dynamicClient,
|
|
934
|
+
instanceId=self.getParam("mas_instance_id"),
|
|
935
|
+
slsLicenseFile=self.slsLicenseFileLocal,
|
|
936
|
+
additionalConfigs=self.additionalConfigsSecret,
|
|
937
|
+
podTemplates=self.podTemplatesSecret
|
|
938
|
+
)
|
|
939
|
+
h.stop_and_persist(symbol=self.successIcon, text=f"Namespace is ready ({pipelinesNamespace})")
|
|
940
|
+
|
|
941
|
+
with Halo(text=f'Testing availability of MAS CLI image in cluster', spinner=self.spinner) as h:
|
|
942
|
+
testCLI()
|
|
943
|
+
h.stop_and_persist(symbol=self.successIcon, text=f"MAS CLI image deployment test completed")
|
|
944
|
+
|
|
945
|
+
with Halo(text=f'Installing latest Tekton definitions (v{self.version})', spinner=self.spinner) as h:
|
|
946
|
+
updateTektonDefinitions(pipelinesNamespace, self.tektonDefsPath)
|
|
947
|
+
h.stop_and_persist(symbol=self.successIcon, text=f"Latest Tekton definitions are installed (v{self.version})")
|
|
948
|
+
|
|
949
|
+
with Halo(text=f"Submitting PipelineRun for {self.getParam('mas_instance_id')} install", spinner=self.spinner) as h:
|
|
950
|
+
pipelineURL = launchInstallPipeline(dynClient=self.dynamicClient, params=self.params)
|
|
951
|
+
if pipelineURL is not None:
|
|
952
|
+
h.stop_and_persist(symbol=self.successIcon, text=f"PipelineRun for {self.getParam('mas_instance_id')} install submitted")
|
|
953
|
+
print_formatted_text(HTML(f"\nView progress:\n <Cyan><u>{pipelineURL}</u></Cyan>\n"))
|
|
954
|
+
else:
|
|
955
|
+
h.stop_and_persist(symbol=self.failureIcon, text=f"Failed to submit PipelineRun for {self.getParam('mas_instance_id')} install, see log file for details")
|
|
956
|
+
print()
|
|
957
|
+
|
|
958
|
+
|
|
959
|
+
if __name__ == '__main__':
|
|
960
|
+
args = installArgParser.parse_args()
|
|
961
|
+
|
|
962
|
+
try:
|
|
963
|
+
app = App()
|
|
964
|
+
app.install(args)
|
|
965
|
+
except KeyboardInterrupt as e:
|
|
966
|
+
pass
|
|
967
|
+
except ApiException as e:
|
|
968
|
+
logger.exception(e, stack_info=True)
|
|
969
|
+
app.fatalError(message=f"An error occured communicating with the target server: {e.reason} ({e.status})")
|
|
970
|
+
except MaxRetryError as e:
|
|
971
|
+
logger.exception(e, stack_info=True)
|
|
972
|
+
app.fatalError(message="Unable to connect to API server", exception=e)
|
|
973
|
+
except TemplateNotFound as e:
|
|
974
|
+
logger.exception(e, stack_info=True)
|
|
975
|
+
app.fatalError("Could not find template", exception=e)
|
|
976
|
+
except KubectlCommandError as e:
|
|
977
|
+
logger.exception(e, stack_info=True)
|
|
978
|
+
app.fatalError("Could not execute kubectl command", exception=e)
|