mas-cli 9.5.0__py3-none-any.whl → 10.0.0__py3-none-any.whl

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

Potentially problematic release.


This version of mas-cli might be problematic. Click here for more details.

Files changed (95) hide show
  1. mas/cli/__init__.py +1 -1
  2. mas/cli/cli.py +82 -65
  3. mas/cli/displayMixins.py +110 -0
  4. mas/cli/gencfg.py +65 -0
  5. mas/cli/install/__init__.py +9 -0
  6. mas/cli/install/argParser.py +792 -0
  7. mas/cli/install/settings/__init__.py +21 -0
  8. mas/cli/install/settings/additionalConfigs.py +150 -0
  9. mas/cli/install/settings/db2Settings.py +173 -0
  10. mas/cli/install/settings/kafkaSettings.py +103 -0
  11. mas/cli/install/settings/manageSettings.py +218 -0
  12. mas/cli/install/settings/turbonomicSettings.py +25 -0
  13. mas/cli/install/summarizer.py +328 -0
  14. mas/cli/templates/ibm-mas-tekton.yaml +10813 -0
  15. mas/cli/templates/jdbccfg.yml.j2 +52 -0
  16. mas/cli/templates/pod-templates/best-effort/ibm-data-dictionary-assetdatadictionary.yml +26 -0
  17. mas/cli/templates/pod-templates/best-effort/ibm-mas-bascfg.yml +56 -0
  18. mas/cli/templates/pod-templates/best-effort/ibm-mas-coreidp.yml +21 -0
  19. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-actions.yml +28 -0
  20. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-auth.yml +32 -0
  21. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-datapower.yml +12 -0
  22. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-devops.yml +14 -0
  23. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-dm.yml +22 -0
  24. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-dsc.yml +40 -0
  25. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-edgeconfig.yml +10 -0
  26. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-fpl.yml +24 -0
  27. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-guardian.yml +20 -0
  28. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-iot.yml +10 -0
  29. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-mbgx.yml +18 -0
  30. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-mfgx.yml +14 -0
  31. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-monitor.yml +18 -0
  32. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-orgmgmt.yml +48 -0
  33. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-provision.yml +28 -0
  34. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-registry.yml +26 -0
  35. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-state.yml +40 -0
  36. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-webui.yml +22 -0
  37. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-healthextaccelerator.yml +13 -0
  38. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-healthextworkspace.yml +10 -0
  39. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-imagestitching.yml +10 -0
  40. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-manageaccelerators.yml +10 -0
  41. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-manageapp.yml +46 -0
  42. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-manageworkspace.yml +48 -0
  43. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-slackproxy.yml +10 -0
  44. mas/cli/templates/pod-templates/best-effort/ibm-mas-pushnotificationcfg.yml +13 -0
  45. mas/cli/templates/pod-templates/best-effort/ibm-mas-scimcfg.yml +14 -0
  46. mas/cli/templates/pod-templates/best-effort/ibm-mas-slscfg.yml +10 -0
  47. mas/cli/templates/pod-templates/best-effort/ibm-mas-smtpcfg.yml +10 -0
  48. mas/cli/templates/pod-templates/best-effort/ibm-mas-suite.yml +136 -0
  49. mas/cli/templates/pod-templates/best-effort/ibm-mas-visualinspection.yml +34 -0
  50. mas/cli/templates/pod-templates/best-effort/ibm-sls-licenseservice.yml +10 -0
  51. mas/cli/templates/pod-templates/guaranteed/ibm-data-dictionary-assetdatadictionary.yml +56 -0
  52. mas/cli/templates/pod-templates/guaranteed/ibm-mas-bascfg.yml +140 -0
  53. mas/cli/templates/pod-templates/guaranteed/ibm-mas-coreidp.yml +45 -0
  54. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-actions.yml +70 -0
  55. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-auth.yml +80 -0
  56. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-datapower.yml +24 -0
  57. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-devops.yml +26 -0
  58. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-dm.yml +52 -0
  59. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-dsc.yml +106 -0
  60. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-edgeconfig.yml +16 -0
  61. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-fpl.yml +62 -0
  62. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-guardian.yml +44 -0
  63. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-iot.yml +16 -0
  64. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-mbgx.yml +42 -0
  65. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-mfgx.yml +32 -0
  66. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-monitor.yml +42 -0
  67. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-orgmgmt.yml +126 -0
  68. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-provision.yml +70 -0
  69. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-registry.yml +62 -0
  70. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-state.yml +106 -0
  71. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-webui.yml +52 -0
  72. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-healthextaccelerator.yml +28 -0
  73. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-healthextworkspace.yml +18 -0
  74. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-imagestitching.yml +16 -0
  75. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-manageaccelerators.yml +16 -0
  76. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-manageapp.yml +106 -0
  77. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-manageworkspace.yml +126 -0
  78. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-slackproxy.yml +16 -0
  79. mas/cli/templates/pod-templates/guaranteed/ibm-mas-pushnotificationcfg.yml +25 -0
  80. mas/cli/templates/pod-templates/guaranteed/ibm-mas-scimcfg.yml +26 -0
  81. mas/cli/templates/pod-templates/guaranteed/ibm-mas-slscfg.yml +16 -0
  82. mas/cli/templates/pod-templates/guaranteed/ibm-mas-smtpcfg.yml +16 -0
  83. mas/cli/templates/pod-templates/guaranteed/ibm-mas-suite.yml +340 -0
  84. mas/cli/templates/pod-templates/guaranteed/ibm-mas-visualinspection.yml +76 -0
  85. mas/cli/templates/pod-templates/guaranteed/ibm-sls-licenseservice.yml +16 -0
  86. mas/cli/templates/pod-templates/saas-essentials/ibm-mas-visualinspection.yml +46 -0
  87. mas/cli/validators.py +126 -0
  88. mas_cli-10.0.0.data/scripts/mas-install +978 -0
  89. {mas_cli-9.5.0.data → mas_cli-10.0.0.data}/scripts/mas-uninstall +11 -8
  90. {mas_cli-9.5.0.data → mas_cli-10.0.0.data}/scripts/mas-upgrade +6 -4
  91. {mas_cli-9.5.0.dist-info → mas_cli-10.0.0.dist-info}/METADATA +2 -1
  92. mas_cli-10.0.0.dist-info/RECORD +94 -0
  93. mas_cli-9.5.0.dist-info/RECORD +0 -8
  94. {mas_cli-9.5.0.dist-info → mas_cli-10.0.0.dist-info}/WHEEL +0 -0
  95. {mas_cli-9.5.0.dist-info → mas_cli-10.0.0.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)