mas-cli 5.1.4__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 (114) hide show
  1. mas/cli/__init__.py +11 -0
  2. mas/cli/aiservice/install/__init__.py +11 -0
  3. mas/cli/aiservice/install/app.py +894 -0
  4. mas/cli/aiservice/install/argBuilder.py +180 -0
  5. mas/cli/aiservice/install/argParser.py +507 -0
  6. mas/cli/aiservice/install/params.py +100 -0
  7. mas/cli/aiservice/install/summarizer.py +134 -0
  8. mas/cli/cli.py +432 -0
  9. mas/cli/displayMixins.py +132 -0
  10. mas/cli/gencfg.py +113 -0
  11. mas/cli/install/__init__.py +11 -0
  12. mas/cli/install/app.py +1316 -0
  13. mas/cli/install/argBuilder.py +465 -0
  14. mas/cli/install/argParser.py +1176 -0
  15. mas/cli/install/catalogs.py +27 -0
  16. mas/cli/install/params.py +172 -0
  17. mas/cli/install/settings/__init__.py +23 -0
  18. mas/cli/install/settings/additionalConfigs.py +227 -0
  19. mas/cli/install/settings/db2Settings.py +252 -0
  20. mas/cli/install/settings/kafkaSettings.py +103 -0
  21. mas/cli/install/settings/manageSettings.py +273 -0
  22. mas/cli/install/settings/mongodbSettings.py +46 -0
  23. mas/cli/install/settings/turbonomicSettings.py +29 -0
  24. mas/cli/install/summarizer.py +398 -0
  25. mas/cli/templates/facilities-configs.yml.j2 +25 -0
  26. mas/cli/templates/ibm-mas-tekton.yaml +49772 -0
  27. mas/cli/templates/jdbccfg.yml.j2 +52 -0
  28. mas/cli/templates/pod-templates/best-effort/ibm-data-dictionary-assetdatadictionary.yml +26 -0
  29. mas/cli/templates/pod-templates/best-effort/ibm-mas-bascfg.yml +56 -0
  30. mas/cli/templates/pod-templates/best-effort/ibm-mas-coreidp.yml +21 -0
  31. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-actions.yml +28 -0
  32. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-auth.yml +32 -0
  33. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-datapower.yml +12 -0
  34. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-devops.yml +14 -0
  35. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-dm.yml +22 -0
  36. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-dsc.yml +40 -0
  37. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-edgeconfig.yml +10 -0
  38. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-fpl.yml +24 -0
  39. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-guardian.yml +20 -0
  40. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-iot.yml +10 -0
  41. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-mbgx.yml +18 -0
  42. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-mfgx.yml +14 -0
  43. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-monitor.yml +18 -0
  44. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-orgmgmt.yml +48 -0
  45. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-provision.yml +28 -0
  46. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-registry.yml +26 -0
  47. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-state.yml +40 -0
  48. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-webui.yml +22 -0
  49. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-healthextaccelerator.yml +13 -0
  50. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-healthextworkspace.yml +10 -0
  51. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-imagestitching.yml +10 -0
  52. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-manageaccelerators.yml +10 -0
  53. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-manageapp.yml +46 -0
  54. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-manageworkspace.yml +48 -0
  55. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-slackproxy.yml +10 -0
  56. mas/cli/templates/pod-templates/best-effort/ibm-mas-pushnotificationcfg.yml +13 -0
  57. mas/cli/templates/pod-templates/best-effort/ibm-mas-scimcfg.yml +14 -0
  58. mas/cli/templates/pod-templates/best-effort/ibm-mas-slscfg.yml +10 -0
  59. mas/cli/templates/pod-templates/best-effort/ibm-mas-smtpcfg.yml +10 -0
  60. mas/cli/templates/pod-templates/best-effort/ibm-mas-suite.yml +136 -0
  61. mas/cli/templates/pod-templates/best-effort/ibm-mas-visualinspection.yml +34 -0
  62. mas/cli/templates/pod-templates/best-effort/ibm-sls-licenseservice.yml +10 -0
  63. mas/cli/templates/pod-templates/guaranteed/ibm-data-dictionary-assetdatadictionary.yml +56 -0
  64. mas/cli/templates/pod-templates/guaranteed/ibm-mas-bascfg.yml +140 -0
  65. mas/cli/templates/pod-templates/guaranteed/ibm-mas-coreidp.yml +45 -0
  66. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-actions.yml +70 -0
  67. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-auth.yml +80 -0
  68. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-datapower.yml +24 -0
  69. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-devops.yml +26 -0
  70. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-dm.yml +52 -0
  71. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-dsc.yml +106 -0
  72. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-edgeconfig.yml +16 -0
  73. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-fpl.yml +62 -0
  74. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-guardian.yml +44 -0
  75. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-iot.yml +16 -0
  76. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-mbgx.yml +42 -0
  77. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-mfgx.yml +32 -0
  78. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-monitor.yml +42 -0
  79. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-orgmgmt.yml +126 -0
  80. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-provision.yml +70 -0
  81. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-registry.yml +62 -0
  82. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-state.yml +106 -0
  83. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-webui.yml +52 -0
  84. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-healthextaccelerator.yml +28 -0
  85. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-healthextworkspace.yml +18 -0
  86. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-imagestitching.yml +16 -0
  87. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-manageaccelerators.yml +16 -0
  88. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-manageapp.yml +106 -0
  89. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-manageworkspace.yml +126 -0
  90. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-slackproxy.yml +16 -0
  91. mas/cli/templates/pod-templates/guaranteed/ibm-mas-pushnotificationcfg.yml +25 -0
  92. mas/cli/templates/pod-templates/guaranteed/ibm-mas-scimcfg.yml +26 -0
  93. mas/cli/templates/pod-templates/guaranteed/ibm-mas-slscfg.yml +16 -0
  94. mas/cli/templates/pod-templates/guaranteed/ibm-mas-smtpcfg.yml +16 -0
  95. mas/cli/templates/pod-templates/guaranteed/ibm-mas-suite.yml +340 -0
  96. mas/cli/templates/pod-templates/guaranteed/ibm-mas-visualinspection.yml +76 -0
  97. mas/cli/templates/pod-templates/guaranteed/ibm-sls-licenseservice.yml +16 -0
  98. mas/cli/templates/suite_mongocfg.yml.j2 +55 -0
  99. mas/cli/uninstall/__init__.py +11 -0
  100. mas/cli/uninstall/app.py +197 -0
  101. mas/cli/uninstall/argParser.py +115 -0
  102. mas/cli/update/__init__.py +11 -0
  103. mas/cli/update/app.py +673 -0
  104. mas/cli/update/argParser.py +156 -0
  105. mas/cli/upgrade/__init__.py +11 -0
  106. mas/cli/upgrade/app.py +164 -0
  107. mas/cli/upgrade/argParser.py +68 -0
  108. mas/cli/upgrade/settings/__init__.py +19 -0
  109. mas/cli/validators.py +151 -0
  110. mas_cli-5.1.4.data/scripts/mas-cli +87 -0
  111. mas_cli-5.1.4.dist-info/METADATA +73 -0
  112. mas_cli-5.1.4.dist-info/RECORD +114 -0
  113. mas_cli-5.1.4.dist-info/WHEEL +5 -0
  114. mas_cli-5.1.4.dist-info/top_level.txt +1 -0
mas/cli/update/app.py ADDED
@@ -0,0 +1,673 @@
1
+ #!/usr/bin/env 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 halo import Halo
15
+ from prompt_toolkit import print_formatted_text, HTML
16
+
17
+ from openshift.dynamic.exceptions import NotFoundError, ResourceNotFoundError
18
+
19
+ from ..cli import BaseApp
20
+ from ..validators import StorageClassValidator
21
+ from .argParser import updateArgParser
22
+
23
+ from mas.devops.ocp import createNamespace, getStorageClasses, getConsoleURL
24
+ from mas.devops.mas import listMasInstances, getCurrentCatalog
25
+ from mas.devops.tekton import preparePipelinesNamespace, installOpenShiftPipelines, updateTektonDefinitions, launchUpdatePipeline
26
+
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ class UpdateApp(BaseApp):
32
+
33
+ def update(self, argv):
34
+ """
35
+ Update MAS instance
36
+ """
37
+ self.args = updateArgParser.parse_args(args=argv)
38
+ self.noConfirm = self.args.no_confirm
39
+ self.devMode = self.args.dev_mode
40
+
41
+ if self.args.mas_catalog_version:
42
+ # Non-interactive mode
43
+ logger.debug("Maximo Operator Catalog version is set, so we assume already connected to the desired OCP")
44
+ requiredParams = ["mas_catalog_version"]
45
+ optionalParams = [
46
+ "db2_namespace",
47
+ "mongodb_namespace",
48
+ "mongodb_v5_upgrade",
49
+ "mongodb_v6_upgrade",
50
+ "mongodb_v7_upgrade",
51
+ "kafka_namespace",
52
+ "kafka_provider",
53
+ "dro_migration",
54
+ "dro_storage_class",
55
+ "dro_namespace",
56
+ "skip_pre_check",
57
+ "dev_mode",
58
+ "cpd_product_version",
59
+ # Dev Mode
60
+ "artifactory_username",
61
+ "artifactory_token"
62
+
63
+ ]
64
+ for key, value in vars(self.args).items():
65
+ # These fields we just pass straight through to the parameters and fail if they are not set
66
+ if key in requiredParams:
67
+ if value is None:
68
+ self.fatalError(f"{key} must be set")
69
+ self.setParam(key, value)
70
+
71
+ # These fields we just pass straight through to the parameters
72
+ elif key in optionalParams:
73
+ if value is not None:
74
+ self.setParam(key, value)
75
+
76
+ # Arguments that we don't need to do anything with
77
+ elif key in ["no_confirm", "help"]:
78
+ pass
79
+
80
+ # Fail if there's any arguments we don't know how to handle
81
+ else:
82
+ print(f"Unknown option: {key} {value}")
83
+ self.fatalError(f"Unknown option: {key} {value}")
84
+ else:
85
+ # Interactive mode
86
+ self.printH1("Set Target OpenShift Cluster")
87
+ # Connect to the target cluster
88
+ self.connect()
89
+
90
+ if self.dynamicClient is None:
91
+ self.fatalError("The Kubernetes dynamic Client is not available. See log file for details")
92
+
93
+ # Perform a check whether the cluster is set up for airgap install, this will trigger an early failure if the cluster is using the now
94
+ # deprecated MaximoApplicationSuite ImageContentSourcePolicy instead of the new ImageDigestMirrorSet
95
+ self.isAirgap()
96
+ self.reviewCurrentCatalog()
97
+ self.reviewMASInstance()
98
+
99
+ if self.args.mas_catalog_version is None:
100
+ # Interactive mode
101
+ self.chooseCatalog()
102
+
103
+ # Validations
104
+ if not self.devMode:
105
+ self.validateCatalog()
106
+
107
+ self.printH1("Dependency Update Checks")
108
+ with Halo(text='Checking for IBM Watson Discovery', spinner=self.spinner) as h:
109
+ if self.isWatsonDiscoveryInstalled():
110
+ h.stop_and_persist(symbol=self.failureIcon, text="IBM Watson Discovery is installed")
111
+ self.fatalError("Watson Discovery is currently installed in the instance of Cloud Pak for Data that is managed by the MAS CLI (in the ibm-cpd namespace), this is no longer supported and the update can not proceed as a result. Please contact IBM support for assistance")
112
+ else:
113
+ h.stop_and_persist(symbol=self.successIcon, text="IBM Watson Discovery is not installed")
114
+
115
+ with Halo(text='Checking for IBM Watson Openscale', spinner=self.spinner) as h:
116
+ if self.isWatsonOpenscaleInstalled():
117
+ h.stop_and_persist(symbol=self.failureIcon, text="IBM Watson Openscale is installed")
118
+ self.fatalError("Watson Openscale is currently installed in the instance of Cloud Pak for Data that is managed by the MAS CLI (in the ibm-cpd namespace), this is no longer supported and the update can not proceed as a result. Please contact IBM support for assistance")
119
+ else:
120
+ h.stop_and_persist(symbol=self.successIcon, text="IBM Watson Openscale is not installed")
121
+
122
+ with Halo(text='Checking for IBM Certificate-Manager', spinner=self.spinner) as h:
123
+ if self.isIBMCertManagerInstalled():
124
+ h.stop_and_persist(symbol=self.successIcon, text="IBM Certificate-Manager will be replaced by Red Hat Certificate-Manager")
125
+ self.setParam("cert_manager_action", "install")
126
+ self.setParam("cert_manager_provider", "redhat")
127
+ self.printHighlight([
128
+ "<u>Migration Notice</u>",
129
+ "IBM Certificate-Manager is currently running in the ${CERT_MANAGER_NAMESPACE} namespace",
130
+ "This will be uninstalled and replaced by Red Hat Certificate-Manager as part of this update",
131
+ ""
132
+ ])
133
+ else:
134
+ h.stop_and_persist(symbol=self.successIcon, text="IBM Certificate-Manager is not installed")
135
+
136
+ self.detectUDS()
137
+ self.detectGrafana4()
138
+ self.detectMongoDb()
139
+ self.detectDb2uOrKafka("db2")
140
+ self.detectDb2uOrKafka("kafka")
141
+ self.detectCP4D()
142
+
143
+ print()
144
+
145
+ self.printH1("Review Settings")
146
+ self.printDescription([
147
+ "Connected to:",
148
+ f" - <u>{getConsoleURL(self.dynamicClient)}</u>"
149
+ ])
150
+
151
+ self.printH2("IBM Maximo Operator Catalog")
152
+ self.printSummary("Installed Catalog", self.installedCatalogId)
153
+ self.printSummary("Updated Catalog", self.getParam("mas_catalog_version"))
154
+
155
+ self.printH2("Supported Dependency Updates")
156
+ if self.getParam("db2_namespace") != "":
157
+ self.printSummary("IBM Db2", f"All Db2uCluster instances in {self.getParam('db2_namespace')}")
158
+ else:
159
+ self.printSummary("IBM Db2", "No action required")
160
+
161
+ if self.getParam("mongodb_namespace") != "":
162
+ self.printSummary("MongoDb CE", f"All MongoDbCommunity instances in {self.getParam('mongodb_namespace')}")
163
+ else:
164
+ self.printSummary("MongoDb CE", "No action required")
165
+
166
+ if self.getParam("kafka_namespace") != "":
167
+ self.printSummary("Apache Kafka", f"All Kafka instances in {self.getParam('kafka_namespace')}")
168
+ else:
169
+ self.printSummary("Apache Kafka", "No action required")
170
+
171
+ if self.getParam("cp4d_update") != "":
172
+ self.printSummary("IBM Cloud Pak for Data", "Platform and services in ibm-cpd")
173
+ else:
174
+ self.printSummary("IBM Cloud Pak for Data", "No action required")
175
+
176
+ self.printH2("Required Migrations")
177
+ self.printSummary("IBM Certificate-Manager", "Migrate to Red Hat Certificate-Manager" if self.getParam("cert_manager_action") != "" else "No action required")
178
+ self.printSummary("IBM User Data Services", "Migrate to IBM Data Reporter Operator" if self.getParam("dro_migration") != "" else "No action required")
179
+ self.printSummary("Grafana v4 Operator", "Migrate to Grafana v5 Operator" if self.getParam("grafana_v5_upgrade") != "" else "No action required")
180
+
181
+ if not self.noConfirm:
182
+ print()
183
+ self.printDescription([
184
+ "Please carefully review your choices above, correcting mistakes now is much easier than after the update has begun"
185
+ ])
186
+ continueWithUpdate = self.yesOrNo("Proceed with these settings")
187
+ # Prepare the namespace and launch the installation pipeline
188
+ if self.noConfirm or continueWithUpdate:
189
+ self.createTektonFileWithDigest()
190
+
191
+ self.printH1("Launch Update")
192
+ pipelinesNamespace = "mas-pipelines"
193
+
194
+ with Halo(text='Validating OpenShift Pipelines installation', spinner=self.spinner) as h:
195
+ installOpenShiftPipelines(self.dynamicClient)
196
+ h.stop_and_persist(symbol=self.successIcon, text="OpenShift Pipelines Operator is installed and ready to use")
197
+
198
+ with Halo(text=f'Preparing namespace ({pipelinesNamespace})', spinner=self.spinner) as h:
199
+ createNamespace(self.dynamicClient, pipelinesNamespace)
200
+ preparePipelinesNamespace(dynClient=self.dynamicClient)
201
+ h.stop_and_persist(symbol=self.successIcon, text=f"Namespace is ready ({pipelinesNamespace})")
202
+
203
+ with Halo(text=f'Installing latest Tekton definitions (v{self.version})', spinner=self.spinner) as h:
204
+ updateTektonDefinitions(pipelinesNamespace, self.tektonDefsPath)
205
+ h.stop_and_persist(symbol=self.successIcon, text=f"Latest Tekton definitions are installed (v{self.version})")
206
+
207
+ with Halo(text="Submitting PipelineRun for MAS update", spinner=self.spinner) as h:
208
+ pipelineURL = launchUpdatePipeline(dynClient=self.dynamicClient, params=self.params)
209
+ if pipelineURL is not None:
210
+ h.stop_and_persist(symbol=self.successIcon, text="PipelineRun for MAS update submitted")
211
+ print_formatted_text(HTML(f"\nView progress:\n <Cyan><u>{pipelineURL}</u></Cyan>\n"))
212
+ else:
213
+ h.stop_and_persist(symbol=self.failureIcon, text="Failed to submit PipelineRun for MAS update, see log file for details")
214
+ print()
215
+
216
+ def reviewCurrentCatalog(self) -> None:
217
+ catalogInfo = getCurrentCatalog(self.dynamicClient)
218
+ self.installedCatalogId = None
219
+ if catalogInfo is None:
220
+ self.fatalError("Unable to locate existing install of the IBM Maximo Operator Catalog")
221
+ elif catalogInfo["catalogId"] is None:
222
+ self.printWarning("Unable to determine identity & version of currently installed ibm-maximo-operator-catalog")
223
+ else:
224
+ self.installedCatalogId = catalogInfo["catalogId"]
225
+ self.printH1("Review Installed Catalog")
226
+ self.printDescription([
227
+ f"The currently installed Maximo Operator Catalog is <u>{catalogInfo['displayName']}</u>",
228
+ f" <u>{catalogInfo['image']}</u>"
229
+ ])
230
+
231
+ def reviewMASInstance(self) -> None:
232
+ self.printH1("Review MAS Instances")
233
+ self.printDescription(["The following MAS intances are installed on the target cluster and will be affected by the catalog update:"])
234
+ try:
235
+ suites = listMasInstances(self.dynamicClient)
236
+ for suite in suites:
237
+ self.printDescription([f"- <u>{suite['metadata']['name']}</u> v{suite['status']['versions']['reconciled']}"])
238
+ except ResourceNotFoundError:
239
+ self.fatalError("No MAS instances were detected on the cluster (Suite.core.mas.ibm.com/v1 API is not available). See log file for details")
240
+
241
+ def chooseCatalog(self) -> None:
242
+ self.printH1("Select IBM Maximo Operator Catalog Version")
243
+ self.printDescription([
244
+ "Select MAS Catalog",
245
+ " 1) July 31 2025 Update (MAS 9.1.1, 9.0.13, 8.11.22, &amp; 8.10.27)",
246
+ " 2) Jun 05 2025 Update (MAS 9.1.0, 9.0.12, 8.11.21, &amp; 8.10.26)",
247
+ " 3) May 01 2025 Update (MAS 9.0.11, 8.11.22, &amp; 8.10.25)",
248
+ " 4) Apr 03 2025 Update (MAS 9.0.10, 8.11.21, &amp; 8.10.24)",
249
+ ])
250
+
251
+ catalogOptions = [
252
+ "v9-250731-amd64", "v9-250624-amd64", "v9-250501-amd64",
253
+ ]
254
+ self.promptForListSelect("Select catalog version", catalogOptions, "mas_catalog_version", default=1)
255
+
256
+ def validateCatalog(self) -> None:
257
+ if self.installedCatalogId is not None and self.installedCatalogId > self.getParam("mas_catalog_version"):
258
+ self.fatalError(f"Selected catalog is older than the currently installed catalog. Unable to update catalog from {self.installedCatalogId} to {self.getParam('mas_catalog_version')}")
259
+
260
+ def isWatsonDiscoveryInstalled(self) -> bool:
261
+ try:
262
+ wdAPI = self.dynamicClient.resources.get(api_version="discovery.watson.ibm.com/v1", kind="WatsonDiscovery")
263
+ wds = wdAPI.get(namespace="ibm-cpd").to_dict()['items']
264
+ if len(wds) > 0:
265
+ return True
266
+ return False
267
+ except (ResourceNotFoundError, NotFoundError):
268
+ # Watson Discovery has never been installed on this cluster
269
+ return False
270
+
271
+ def isWatsonOpenscaleInstalled(self) -> bool:
272
+ try:
273
+ wosAPI = self.dynamicClient.resources.get(api_version="wos.cpd.ibm.com/v1", kind="WOService")
274
+ wos = wosAPI.get(namespace="ibm-cpd").to_dict()['items']
275
+ if len(wos) > 0:
276
+ return True
277
+ return False
278
+ except (ResourceNotFoundError, NotFoundError):
279
+ # Watson Openscale has never been installed on this cluster
280
+ return False
281
+
282
+ def isIBMCertManagerInstalled(self) -> bool:
283
+ """
284
+ Check whether the deprecated IBM Certificate-Manager is installed, if it is then we will
285
+ automatically migrate to Red Hat Certificate-Manager
286
+ """
287
+
288
+ try:
289
+ # Check if 'ibm-common-services' namespace exist, this will throw NotFoundError exception when not found
290
+ namespaceAPI = self.dynamicClient.resources.get(api_version="v1", kind="Namespace")
291
+ namespaceAPI.get(name="ibm-common-services")
292
+
293
+ podsAPI = self.dynamicClient.resources.get(api_version="v1", kind="Pod")
294
+ podsList = podsAPI.get(namespace="ibm-common-services")
295
+ for pod in podsList.items:
296
+ if pod is not None and "cert-manager-cainjector" in pod.metadata.name:
297
+ logger.debug("Found IBM Certificate-Manager in ibm-common-services namespace")
298
+ return True
299
+ logger.debug("There is an ibm-common-services namespace, but we did not find the IBM Certificate-Manager installation")
300
+ return False
301
+ except NotFoundError:
302
+ logger.debug("There is no ibm-common-services namespace")
303
+ return False
304
+
305
+ def detectGrafana4(self) -> bool:
306
+ with Halo(text='Checking for Grafana Operator v4', spinner=self.spinner) as h:
307
+ try:
308
+ grafanaAPI = self.dynamicClient.resources.get(api_version="integreatly.org/v1alpha1", kind="Grafana")
309
+ grafanaVersion4s = grafanaAPI.get().to_dict()["items"]
310
+
311
+ # For testing, comment out the lines above and set grafanaVersion4s to a simple list
312
+ # grafanaVersion4s = ["hello"]
313
+ if len(grafanaVersion4s) > 0:
314
+ h.stop_and_persist(symbol=self.successIcon, text="Grafana Operator v4 instance will be updated to v5")
315
+ self.printDescription([
316
+ "<u>Dependency Upgrade Notice</u>",
317
+ "Grafana Operator v4 is currently installed and will be updated to v5",
318
+ "- Grafana v5 instance will have a new URL and admin password",
319
+ "- User accounts set up in the v4 instance will not be migrated"
320
+ ])
321
+ self.setParam("grafana_v5_upgrade", "true")
322
+ else:
323
+ h.stop_and_persist(symbol=self.successIcon, text="Grafana Operator v4 is not installed")
324
+ return
325
+ except (ResourceNotFoundError, NotFoundError):
326
+ h.stop_and_persist(symbol=self.successIcon, text="Grafana Operator v4 is not installed")
327
+
328
+ def detectMongoDb(self) -> None:
329
+ with Halo(text='Checking for MongoDb CE', spinner=self.spinner) as h:
330
+ # TODO: Replace this with a lookup to just use whatever is already set up
331
+ # because we should not be changing the scale of the mongodb cluster during
332
+ # and update
333
+ if self.isSNO():
334
+ self.setParam("mongodb_replicas", "1")
335
+ else:
336
+ self.setParam("mongodb_replicas", "3")
337
+
338
+ # Determine the namespace
339
+ try:
340
+ mongoDbAPI = self.dynamicClient.resources.get(api_version="mongodbcommunity.mongodb.com/v1", kind="MongoDBCommunity")
341
+ mongoClusters = mongoDbAPI.get().to_dict()["items"]
342
+
343
+ if len(mongoClusters) > 0:
344
+ mongoNamespace = mongoClusters[0]["metadata"]["namespace"]
345
+ currentMongoVersion = mongoClusters[0]["status"]["version"]
346
+
347
+ self.setParam("mongodb_namespace", mongoNamespace)
348
+
349
+ # Important:
350
+ # This CLI can run independent of the ibm.mas_devops collection, so we cannot reference
351
+ # the case bundles in there anymore
352
+ # Longer term we will centralise this information inside the mas-devops python collection,
353
+ # where it can be made available to both the ansible collection and this python package.
354
+ defaultMongoVersion = "7.0.12"
355
+ mongoVersions = {
356
+ "v9-240625-amd64": "6.0.12",
357
+ "v9-240730-amd64": "6.0.12",
358
+ "v9-240827-amd64": "6.0.12",
359
+ "v9-241003-amd64": "6.0.12",
360
+ "v9-241107-amd64": "7.0.12",
361
+ "v9-241205-amd64": "7.0.12",
362
+ "v9-250109-amd64": "7.0.12",
363
+ "v9-250206-amd64": "7.0.12",
364
+ "v9-250306-amd64": "7.0.12",
365
+ "v9-250403-amd64": "7.0.12",
366
+ "v9-250501-amd64": "7.0.12",
367
+ "v9-250624-amd64": "7.0.12",
368
+ "v9-250731-amd64": "7.0.22",
369
+ }
370
+ catalogVersion = self.getParam('mas_catalog_version')
371
+ if catalogVersion in mongoVersions:
372
+ targetMongoVersion = mongoVersions[self.getParam('mas_catalog_version')]
373
+ else:
374
+ targetMongoVersion = defaultMongoVersion
375
+
376
+ self.setParam("mongodb_version", targetMongoVersion)
377
+
378
+ targetMongoVersionMajor = targetMongoVersion.split(".")[0]
379
+ currentMongoVersionMajor = currentMongoVersion.split(".")[0]
380
+
381
+ if targetMongoVersionMajor > currentMongoVersionMajor:
382
+ self.setParam("mongodb_action", "install")
383
+ # Let users know that Mongo will be upgraded if existing MongoDb major.minor version
384
+ # is lower than the target major version
385
+ # We don't show this message for normal updates, e.g. 5.0.1 to 5.0.2
386
+ if self.noConfirm and self.getParam(f"mongodb_v{targetMongoVersionMajor}_upgrade") != "true":
387
+ # The user has chosen not to provide confirmation but has not provided the flag to pre-approve the mongo major version update
388
+ h.stop_and_persist(symbol=self.failureIcon, text=f"MongoDb CE {currentMongoVersion} needs to be updated to {targetMongoVersion}")
389
+ self.showMongoDependencyUpdateNotice(currentMongoVersion, targetMongoVersion)
390
+ self.fatalError(f"By choosing {self.getParam('mas_catalog_version')} you must confirm MongoDb update to version {targetMongoVersionMajor} using '--mongodb-v{targetMongoVersionMajor}-upgrade' when using '--no-confirm'")
391
+ elif self.getParam(f"mongodb_v{targetMongoVersionMajor}_upgrade") != "true":
392
+ # The user has not pre-approved the major version update
393
+ h.stop_and_persist(symbol=self.successIcon, text=f"MongoDb CE {currentMongoVersion} needs to be updated to {targetMongoVersion}")
394
+ self.showMongoDependencyUpdateNotice(currentMongoVersion, targetMongoVersion)
395
+ if not self.yesOrNo(f"Confirm update from MongoDb {currentMongoVersion} to {targetMongoVersion}", f"mongodb_v{targetMongoVersionMajor}_upgrade"):
396
+ # If the user did not approve the update, abort
397
+ exit(1)
398
+ print()
399
+ else:
400
+ h.stop_and_persist(symbol=self.successIcon, text=f"MongoDb CE will be updated from {currentMongoVersion} to {targetMongoVersion}")
401
+ self.showMongoDependencyUpdateNotice(currentMongoVersion, targetMongoVersion)
402
+ elif targetMongoVersion < currentMongoVersion:
403
+ h.stop_and_persist(symbol=self.failureIcon, text=f"MongoDb CE {currentMongoVersion} cannot be downgraded to {targetMongoVersion}")
404
+ self.showMongoDependencyUpdateNotice(currentMongoVersion, targetMongoVersion)
405
+ self.fatalError(f"Existing MongoDB Community Edition installation at version {currentMongoVersion} cannot be downgraded to version {targetMongoVersion}")
406
+ else:
407
+ h.stop_and_persist(symbol=self.successIcon, text=f"MongoDb CE is already installed at version {targetMongoVersion}")
408
+ else:
409
+ # There's no MongoDb instance installed in the cluster, so nothing to do
410
+ h.stop_and_persist(symbol=self.successIcon, text="No MongoDb CE instances found")
411
+ except (ResourceNotFoundError, NotFoundError):
412
+ # There's no MongoDb instance installed in the cluster, so nothing to do
413
+ h.stop_and_persist(symbol=self.successIcon, text="MongoDb CE is not installed")
414
+
415
+ def showMongoDependencyUpdateNotice(self, currentMongoVersion, targetMongoVersion) -> None:
416
+ self.printHighlight([
417
+ "",
418
+ "<u>Dependency Update Notice</u>",
419
+ f"MongoDB Community Edition is currently running version {currentMongoVersion} and will be updated to {targetMongoVersion}",
420
+ "It is recommended that you backup your MongoDB instance before proceeding:",
421
+ " <u>https://www.ibm.com/docs/en/mas-cd/continuous-delivery?topic=suite-backing-up-mongodb-maximo-application</u>",
422
+ ""
423
+ ])
424
+
425
+ def showUDSUpdateNotice(self) -> None:
426
+ self.printHighlight([
427
+ "",
428
+ "<u>Dependency Update Notice</u>",
429
+ "IBM User Data Services (UDS) is currently installed and will be replaced by IBM Data Reporter Operator (DRO)",
430
+ "UDS will be uninstalled and <u>all MAS instances</u> will be re-configured to use DRO",
431
+ ""
432
+ ])
433
+
434
+ def selectDROStorageclass(self):
435
+ self.printDescription([
436
+ "",
437
+ "Select the storage class for DRO to use from the list below:"
438
+ ])
439
+ for storageClass in getStorageClasses(self.dynamicClient):
440
+ print_formatted_text(HTML(f"<LightSlateGrey> - {storageClass.metadata.name}</LightSlateGrey>"))
441
+ self.promptForString("DRO storage class", "dro_storage_class", validator=StorageClassValidator())
442
+
443
+ def detectUDS(self) -> None:
444
+ with Halo(text='Checking for IBM User Data Services', spinner=self.spinner) as h:
445
+ try:
446
+ analyticsProxyAPI = self.dynamicClient.resources.get(api_version="uds.ibm.com/v1", kind="AnalyticsProxy")
447
+ analyticsProxies = analyticsProxyAPI.get(namespace="ibm-common-services").to_dict()['items']
448
+
449
+ # Useful for testing: comment out the two lines above and set analyticsProxies to a
450
+ # simple list to trigger to UDS migration logic.
451
+ # analyticsProxies = ["foo"]
452
+ if len(analyticsProxies) == 0:
453
+ logger.debug("UDS is not currently installed on this cluster")
454
+ h.stop_and_persist(symbol=self.successIcon, text="IBM User Data Services is not installed")
455
+ else:
456
+ h.stop_and_persist(symbol=self.successIcon, text="IBM User Data Services must be migrated to IBM Data Reporter Operator")
457
+
458
+ if self.noConfirm and self.getParam("dro_migration") != "true":
459
+ # The user has chosen not to provide confirmation but has not provided the flag to pre-approve the migration
460
+ h.stop_and_persist(symbol=self.failureIcon, text="IBM User Data Services needs to be migrated to IBM Data Reporter Operator")
461
+ self.showUDSUpdateNotice()
462
+ self.fatalError(f"By choosing {self.getParam('mas_catalog_version')} you must confirm the migration to DRO using '--dro-migration' when using '--no-confirm'")
463
+ elif self.noConfirm and self.getParam("dro_storage_class") is None:
464
+ # The user has not provided the storage class to use for DRO, but has disabled confirmations/interactive prompts
465
+ h.stop_and_persist(symbol=self.failureIcon, text="IBM User Data Services needs to be migrated to IBM Data Reporter Operator")
466
+ self.showUDSUpdateNotice()
467
+ self.fatalError(f"By choosing {self.getParam('mas_catalog_version')} you must provide the storage class to use for the migration to DRO using '--dro-storage-class' when using '--no-confirm'")
468
+ else:
469
+ self.showUDSUpdateNotice()
470
+ if self.getParam("dro_migration") != "true":
471
+ if not self.yesOrNo("Confirm migration from UDS to DRO", "dro_migration"):
472
+ # If the user did not approve the update, abort
473
+ exit(1)
474
+
475
+ if self.getParam("dro_storage_class") is None or self.getParam("dro_storage_class") == "":
476
+ self.selectDROStorageclass()
477
+
478
+ if self.getParam("dro_migration") == "true":
479
+ self.setParam("uds_action", "install-dro")
480
+
481
+ except (ResourceNotFoundError, NotFoundError):
482
+ # UDS has never been installed on this cluster
483
+ logger.debug("UDS has not been installed on this cluster before")
484
+ h.stop_and_persist(symbol=self.successIcon, text="IBM User Data Services is not installed")
485
+
486
+ def detectCP4D(self) -> bool:
487
+ # Important:
488
+ # This CLI can run independent of the ibm.mas_devops collection, so we cannot reference
489
+ # the case bundles in there anymore
490
+ # Longer term we will centralise this information inside the mas-devops python collection,
491
+ # where it can be made available to both the ansible collection and this python package.
492
+ cp4dVersions = {
493
+ "v9-240625-amd64": "4.8.0",
494
+ "v9-240730-amd64": "4.8.0",
495
+ "v9-240827-amd64": "4.8.0",
496
+ "v9-241003-amd64": "4.8.0",
497
+ "v9-241107-amd64": "4.8.0",
498
+ "v9-241205-amd64": "5.0.0",
499
+ "v9-250109-amd64": "5.0.0",
500
+ "v9-250206-amd64": "5.0.0",
501
+ "v9-250306-amd64": "5.0.0",
502
+ "v9-250403-amd64": "5.0.0",
503
+ "v9-250501-amd64": "5.0.0",
504
+ "v9-250624-amd64": "5.1.3",
505
+ "v9-250731-amd64": "5.1.3",
506
+ }
507
+
508
+ with Halo(text='Checking for IBM Cloud Pak for Data', spinner=self.spinner) as h:
509
+ try:
510
+ cpdAPI = self.dynamicClient.resources.get(api_version="cpd.ibm.com/v1", kind="Ibmcpd")
511
+ cpds = cpdAPI.get().to_dict()["items"]
512
+
513
+ # For testing, comment out the lines above and set cpds to a simple list
514
+ # cpds = [{
515
+ # "metadata": {"namespace": "ibm-cpd" },
516
+ # "spec": {
517
+ # "version": "4.6.6",
518
+ # "storageClass": "default",
519
+ # "zenCoreMetadbStorageClass": "default"
520
+ # }
521
+ # }]
522
+
523
+ if len(cpds) > 0:
524
+ cpdInstanceNamespace = cpds[0]["metadata"]["namespace"]
525
+ cpdInstanceVersion = cpds[0]["spec"]["version"]
526
+ if self.args.cpd_product_version:
527
+ cpdTargetVersion = self.getParam("cpd_product_version")
528
+ else:
529
+ cpdTargetVersion = cp4dVersions[self.getParam("mas_catalog_version")]
530
+
531
+ currentCpdVersionMajorMinor = f"{cpdInstanceVersion.split('.')[0]}.{cpdInstanceVersion.split('.')[1]}"
532
+ targetCpdVersionMajorMinor = f"{cpdTargetVersion.split('.')[0]}.{cpdTargetVersion.split('.')[1]}"
533
+
534
+ if cpdInstanceVersion < cpdTargetVersion:
535
+ # We have to update CP4D
536
+ h.stop_and_persist(symbol=self.successIcon, text=f"IBM Cloud Pak for Data ({cpdInstanceNamespace}) {cpdInstanceVersion} needs to be updated to {cpdTargetVersion}")
537
+
538
+ if currentCpdVersionMajorMinor < targetCpdVersionMajorMinor:
539
+ # We only show the "backup first" notice for minor CP4D updates
540
+ self.printHighlight([
541
+ ""
542
+ "<u>Dependency Update Notice</u>",
543
+ f"Cloud Pak For Data ({cpdInstanceNamespace}) is currently running version {cpdInstanceVersion} and will be updated to version {cpdTargetVersion}",
544
+ "It is recommended that you backup your Cloud Pak for Data instance before proceeding:",
545
+ " <u>https://www.ibm.com/docs/en/cloud-paks/cp-data/5.0.x?topic=administering-backing-up-restoring-cloud-pak-data</u>"
546
+ ])
547
+
548
+ # Lookup the storage classes already used by CP4D
549
+ # Note: this should be done by the Ansible role, but isn't
550
+ if "storageClass" in cpds[0]["spec"]:
551
+ cpdFileStorage = cpds[0]["spec"]["storageClass"]
552
+ elif "fileStorageClass" in cpds[0]["spec"]:
553
+ cpdFileStorage = cpds[0]["spec"]["fileStorageClass"]
554
+ else:
555
+ self.fatalError("Unable to determine the file storage class used in IBM Cloud Pak for Data")
556
+
557
+ if "zenCoreMetadbStorageClass" in cpds[0]["spec"]:
558
+ cpdBlockStorage = cpds[0]["spec"]["zenCoreMetadbStorageClass"]
559
+ elif "blockStorageClass" in cpds[0]["spec"]:
560
+ cpdBlockStorage = cpds[0]["spec"]["blockStorageClass"]
561
+ else:
562
+ self.fatalError("Unable to determine the block storage class used in IBM Cloud Pak for Data")
563
+
564
+ # Set the desired storage classes (the same ones already in use)
565
+ self.setParam("storage_class_rwx", cpdFileStorage)
566
+ self.setParam("storage_class_rwo", cpdBlockStorage)
567
+
568
+ # Set the desired target version
569
+ self.setParam("cpd_product_version", cpdTargetVersion)
570
+ self.setParam("cp4d_update", "true")
571
+ self.setParam("skip_entitlement_key_flag", "true")
572
+
573
+ self.detectCpdService('WS', 'ws.cpd.ibm.com/v1beta1', 'Watson Studio', "cp4d_update_ws")
574
+ self.detectCpdService('WmlBase', 'wml.cpd.ibm.com/v1beta1', 'Watson Machine Learning', "cp4d_update_wml")
575
+ self.detectCpdService('AnalyticsEngine', 'ae.cpd.ibm.com/v1', 'Analytics Engine', "cp4d_update_spark")
576
+ self.detectCpdService('WOService', 'wos.cpd.ibm.com/v1', 'Watson Openscale', "cp4d_update_wos")
577
+ self.detectCpdService('Spss', 'spssmodeler.cpd.ibm.com/v1', 'SPSS Modeler', "cp4d_update_spss")
578
+ self.detectCpdService('CAService', 'ca.cpd.ibm.com/v1', 'Cognos Analytics', "cp4d_update_cognos")
579
+ else:
580
+ h.stop_and_persist(symbol=self.successIcon, text=f"IBM Cloud Pak for Data ({cpdInstanceNamespace}) is already installed at version {cpdTargetVersion}")
581
+ else:
582
+ h.stop_and_persist(symbol=self.successIcon, text="No IBM Cloud Pak for Data instance found")
583
+ except (ResourceNotFoundError, NotFoundError):
584
+ h.stop_and_persist(symbol=self.successIcon, text="IBM Cloud Pak for Data is not installed")
585
+
586
+ def detectCpdService(self, kind: str, api: str, name: str, param: str) -> None:
587
+ try:
588
+ cpdServiceAPI = self.dynamicClient.resources.get(api_version=api, kind=kind)
589
+ cpdServices = cpdServiceAPI.get().to_dict()["items"]
590
+
591
+ if len(cpdServices) > 0:
592
+ logger.debug(f"{name} is included in CP4D update")
593
+ self.setParam(param, "true")
594
+ else:
595
+ logger.debug(f"{name} is not included in CP4D update")
596
+ self.setParam(param, "false")
597
+
598
+ except (ResourceNotFoundError, NotFoundError) as e:
599
+ # No action required for this service
600
+ logger.debug(f"{name} is not included in CP4D update: {e}")
601
+ self.setParam(param, "false")
602
+
603
+ def detectDb2uOrKafka(self, mode: str) -> bool:
604
+ if mode == "db2":
605
+ haloStartingMessage = "Checking for Db2uCluster instances to update"
606
+ apiVersion = "db2u.databases.ibm.com/v1"
607
+ kind = "Db2uCluster"
608
+ paramName = "db2_namespace"
609
+ elif mode == "kafka":
610
+ haloStartingMessage = "Checking for Kafka instances to update"
611
+ apiVersion = "kafka.strimzi.io/v1beta2"
612
+ kind = "Kafka"
613
+ paramName = "kafka_namespace"
614
+ else:
615
+ self.fatalError("Unexpected error")
616
+
617
+ with Halo(text=haloStartingMessage, spinner=self.spinner) as h:
618
+ try:
619
+ k8sAPI = self.dynamicClient.resources.get(api_version=apiVersion, kind=kind)
620
+ instances = k8sAPI.get().to_dict()["items"]
621
+
622
+ logger.debug(f"Found {len(instances)} {kind} instances on the cluster")
623
+ if len(instances) > 0:
624
+ # If the user provided the namespace using --db2-namespace then we don't have any work to do here
625
+ if self.getParam(paramName) == "":
626
+ namespaces = set()
627
+ for instance in instances:
628
+ namespaces.add(instance["metadata"]["namespace"])
629
+
630
+ if len(namespaces) == 1:
631
+ # If db2u is only in one namespace, we will update that
632
+ h.stop_and_persist(symbol=self.successIcon, text=f"{len(instances)} {kind}s ({apiVersion}) in namespace '{list(namespaces)[0]}' will be updated")
633
+ logger.debug(f"There is only one namespace containing {kind}s so we will target that one: {namespaces}")
634
+ self.setParam(paramName, list(namespaces)[0])
635
+ elif self.noConfirm:
636
+ # If db2u is in multiple namespaces and user has disabled prompts then we must error
637
+ h.stop_and_persist(symbol=self.failureIcon, text=f"{len(instances)} {kind}s ({apiVersion}) were found in multiple namespaces")
638
+ logger.warning(f"There are multiple namespaces containing {kind}s and user has enable --no-confirm without setting --{mode}-namespace: {namespaces.keys()}")
639
+ self.fatalError(f"{kind}s are installed in multiple namespaces. You must instruct which one to update using the '--{mode}-namespace' argument")
640
+ else:
641
+ # Otherwise, provide user the list of namespaces we found and ask them to pick on
642
+ h.stop_and_persist(symbol=self.successIcon, text=f"{len(instances)} {kind}s ({apiVersion}) found in multiple namespaces")
643
+ logger.debug(f"There are multiple namespaces containing {kind}s, user must choose: {namespaces}")
644
+ self.printDescription([
645
+ f"{kind}s were found in multiple namespaces, select the namespace to target from the list below:"
646
+ ])
647
+ for index, ns in enumerate(sorted(namespaces), start=1):
648
+ self.printDescription([f"{index}. {ns}"])
649
+ self.promptForListSelect("Select namespace", sorted(namespaces), paramName)
650
+ else:
651
+ logger.debug(f"Found no instances of {kind} to update")
652
+ h.stop_and_persist(symbol=self.successIcon, text=f"Found no {kind} ({apiVersion}) instances to update")
653
+ except (ResourceNotFoundError, NotFoundError):
654
+ logger.debug(f"{kind}.{apiVersion} is not available in the cluster")
655
+ h.stop_and_persist(symbol=self.successIcon, text=f"{kind}.{apiVersion} is not available in the cluster")
656
+
657
+ # With Kafka we also have to determine the provider (strimzi or redhat)
658
+ if mode == "kafka" and self.getParam("kafka_namespace") != "" and self.getParam("kafka_provider") == "":
659
+ try:
660
+ subAPI = self.dynamicClient.resources.get(api_version="operators.coreos.com/v1alpha1", kind="Subscription")
661
+ subs = subAPI.get().to_dict()["items"]
662
+
663
+ for sub in subs:
664
+ if sub["spec"]["name"] == "amq-streams":
665
+ self.setParam("kafka_provider", "redhat")
666
+ elif sub["spec"]["name"] == "strimzi-kafka-operator":
667
+ self.setParam("kafka_provider", "strimzi")
668
+ except (ResourceNotFoundError, NotFoundError):
669
+ pass
670
+
671
+ # If the param is still undefined then there is a big problem
672
+ if self.getParam("kafka_provider") == "":
673
+ self.fatalError("Unable to determine whether the installed Kafka instance is managed by Strimzi or Red Hat AMQ Streams")