mas-cli 11.5.0__py3-none-any.whl → 11.7.0__py3-none-any.whl

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

Potentially problematic release.


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

mas/cli/install/app.py CHANGED
@@ -29,6 +29,8 @@ from .argBuilder import installArgBuilderMixin
29
29
  from .argParser import installArgParser
30
30
  from .settings import InstallSettingsMixin
31
31
  from .summarizer import InstallSummarizerMixin
32
+ from .params import requiredParams, optionalParams
33
+ from .catalogs import catalogChoices
32
34
 
33
35
  from mas.cli.validators import (
34
36
  InstanceIDFormatValidator,
@@ -52,7 +54,17 @@ from mas.devops.tekton import (
52
54
  logger = logging.getLogger(__name__)
53
55
 
54
56
 
57
+ def logMethodCall(func):
58
+ def wrapper(self, *args, **kwargs):
59
+ logger.debug(f">>> InstallApp.{func.__name__}")
60
+ result = func(self, *args, **kwargs)
61
+ logger.debug(f"<<< InstallApp.{func.__name__}")
62
+ return result
63
+ return wrapper
64
+
65
+
55
66
  class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGeneratorMixin, installArgBuilderMixin):
67
+ @logMethodCall
56
68
  def validateCatalogSource(self):
57
69
  catalogsAPI = self.dynamicClient.resources.get(api_version="operators.coreos.com/v1alpha1", kind="CatalogSource")
58
70
  try:
@@ -75,6 +87,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
75
87
  # There's no existing catalog installed
76
88
  pass
77
89
 
90
+ @logMethodCall
78
91
  def validateInternalRegistryAvailable(self):
79
92
  """
80
93
  We can save customers wasted time by detecting if the image-registry service
@@ -93,6 +106,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
93
106
  ])
94
107
  )
95
108
 
109
+ @logMethodCall
96
110
  def licensePrompt(self):
97
111
  licenses = {
98
112
  "8.9.x": " - <u>https://ibm.biz/MAS89-License</u>",
@@ -114,6 +128,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
114
128
  if not self.yesOrNo("Do you accept the license terms"):
115
129
  exit(1)
116
130
 
131
+ @logMethodCall
117
132
  def configICR(self):
118
133
  if self.devMode:
119
134
  self.setParam("mas_icr_cp", getenv("MAS_ICR_CP", "docker-na-public.artifactory.swg-devops.com/wiotp-docker-local"))
@@ -124,6 +139,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
124
139
  self.setParam("mas_icr_cpopen", getenv("MAS_ICR_CPOPEN", "icr.io/cpopen"))
125
140
  self.setParam("sls_icr_cpopen", getenv("SLS_ICR_CPOPEN", "icr.io/cpopen"))
126
141
 
142
+ @logMethodCall
127
143
  def configICRCredentials(self):
128
144
  self.printH1("Configure IBM Container Registry")
129
145
  self.promptForString("IBM entitlement key", "ibm_entitlement_key", isPassword=True)
@@ -131,11 +147,13 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
131
147
  self.promptForString("Artifactory username", "artifactory_username")
132
148
  self.promptForString("Artifactory token", "artifactory_token", isPassword=True)
133
149
 
150
+ @logMethodCall
134
151
  def configCertManager(self):
135
152
  # Only install of Red Hat Cert-Manager has been supported since the January 2025 catalog update
136
153
  self.setParam("cert_manager_provider", "redhat")
137
154
  self.setParam("cert_manager_action", "install")
138
155
 
156
+ @logMethodCall
139
157
  def configCatalog(self):
140
158
  self.printH1("IBM Maximo Operator Catalog Selection")
141
159
  if self.devMode:
@@ -148,6 +166,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
148
166
  self.setParam("mas_catalog_version", self.installOptions[catalogSelection - 1]["catalog"])
149
167
  self.setParam("mas_channel", self.installOptions[catalogSelection - 1]["release"])
150
168
 
169
+ @logMethodCall
151
170
  def configSLS(self) -> None:
152
171
  self.printH1("Configure Product License")
153
172
  self.slsLicenseFileLocal = self.promptForFile("License file", mustExist=True, envVar="SLS_LICENSE_FILE_LOCAL")
@@ -155,42 +174,52 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
155
174
  self.promptForString("Contact first name", "uds_contact_firstname")
156
175
  self.promptForString("Contact last name", "uds_contact_lastname")
157
176
 
158
- self.promptForString("IBM Data Reporter Operator (DRO) Namespace", "dro_namespace", default="redhat-marketplace")
177
+ if self.showAdvancedOptions:
178
+ self.promptForString("IBM Suite License Services (SLS) Namespace", "sls_namespace", default="ibm-sls")
179
+ self.promptForString("IBM Data Reporter Operator (DRO) Namespace", "dro_namespace", default="redhat-marketplace")
159
180
 
181
+ @logMethodCall
160
182
  def selectLocalConfigDir(self) -> None:
161
183
  if self.localConfigDir is None:
162
184
  # You need to tell us where the configuration file can be found
163
185
  self.localConfigDir = self.promptForDir("Select Local configuration directory")
164
186
 
187
+ @logMethodCall
165
188
  def configGrafana(self) -> None:
166
- try:
167
- packagemanifestAPI = self.dynamicClient.resources.get(api_version="packages.operators.coreos.com/v1", kind="PackageManifest")
168
- packagemanifestAPI.get(name="grafana-operator", namespace="openshift-marketplace")
169
- if self.skipGrafanaInstall:
170
- self.setParam("grafana_action", "none")
171
- else:
172
- self.setParam("grafana_action", "install")
173
- except NotFoundError:
189
+ if self.architecture == "s390x":
190
+ # We are not supporting Grafana on s390x at the moment
174
191
  self.setParam("grafana_action", "none")
192
+ else:
193
+ try:
194
+ packagemanifestAPI = self.dynamicClient.resources.get(api_version="packages.operators.coreos.com/v1", kind="PackageManifest")
195
+ packagemanifestAPI.get(name="grafana-operator", namespace="openshift-marketplace")
196
+ if self.skipGrafanaInstall:
197
+ self.setParam("grafana_action", "none")
198
+ else:
199
+ self.setParam("grafana_action", "install")
200
+ except NotFoundError:
201
+ self.setParam("grafana_action", "none")
175
202
 
176
- if self.interactiveMode:
177
- self.printH1("Configure Grafana")
178
- if self.getParam("grafana_action") == "none":
179
- print_formatted_text("The Grafana operator package is not available in any catalogs on the target cluster, the installation of Grafana will be disabled")
180
- else:
181
- self.promptForString("Install namespace", "grafana_v5_namespace", default="grafana5")
182
- self.promptForString("Grafana storage size", "grafana_instance_storage_size", default="10Gi")
183
-
184
- def configMongoDb(self) -> None:
185
- self.printH1("Configure MongoDb")
186
- self.promptForString("Install namespace", "mongodb_namespace", default="mongoce")
203
+ if self.interactiveMode and self.showAdvancedOptions:
204
+ self.printH1("Configure Grafana")
205
+ if self.getParam("grafana_action") == "none":
206
+ print_formatted_text("The Grafana operator package is not available in any catalogs on the target cluster, the installation of Grafana will be disabled")
207
+ else:
208
+ self.promptForString("Install namespace", "grafana_v5_namespace", default="grafana5")
209
+ self.promptForString("Grafana storage size", "grafana_instance_storage_size", default="10Gi")
187
210
 
211
+ @logMethodCall
188
212
  def configSpecialCharacters(self):
189
- self.printH1("Configure special characters for userID and username")
190
- self.yesOrNo("Do you want to allow special characters for user IDs and usernames?", "mas_special_characters")
213
+ if self.showAdvancedOptions:
214
+ self.printH1("Configure special characters for userID and username")
215
+ self.printDescription([
216
+ "By default Maximo Application Suite will not allow special characters in usernames and userIDs, and this is the recommended setting. However, legacy Maximo products allowed this, so for maximum compatibilty when migrating from EAM 7 you can choose to enable this support."
217
+ ])
218
+ self.yesOrNo("Allow special characters for user IDs and usernames", "mas_special_characters")
191
219
 
220
+ @logMethodCall
192
221
  def configCP4D(self):
193
- if self.getParam("mas_catalog_version") in ["v9-240625-amd64", "v9-240730-amd64", "v9-240827-amd64", "v9-241003-amd64"]:
222
+ if self.getParam("mas_catalog_version") in ["v9-240625-amd64", "v9-240730-amd64", "v9-240827-amd64", "v9-241003-amd64", "v9-241107-amd64"]:
194
223
  logger.debug(f"Using automatic CP4D product version: {self.getParam('cpd_product_version')}")
195
224
  self.setParam("cpd_product_version", "4.8.0")
196
225
  elif self.getParam("cpd_product_version") == "":
@@ -205,37 +234,42 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
205
234
  logger.debug(f"Using user-provided (flags) CP4D product version: {self.getParam('cpd_product_version')}")
206
235
  self.deployCP4D = True
207
236
 
237
+ @logMethodCall
208
238
  def configSSOProperties(self):
209
- self.printH1("Single Sign-On (SSO)")
210
- self.printDescription([
211
- "Many aspects of Maximo Application Suite's Single Sign-On (SSO) can be customized:",
212
- " - Idle session automatic logout timer",
213
- " - Session, access token, and refresh token timeouts",
214
- " - Default identity provider (IDP), and seamless login",
215
- " - Brower cookie properties"
216
- ])
217
- if self.yesOrNo("Configure SSO properties"):
218
- self.promptForInt("Idle session logout timer (seconds)", "idle_timeout")
219
- self.promptForString("Session timeout (e.g. '12h' for 12 hours)", "idp_session_timeout", validator=TimeoutFormatValidator())
220
- self.promptForString("Access token timeout (e.g. '30m' for 30 minutes)", "access_token_timeout", validator=TimeoutFormatValidator())
221
- self.promptForString("Refresh token timeout (e.g. '12h' for 12 hours)", "refresh_token_timeout", validator=TimeoutFormatValidator())
222
- self.promptForString("Default Identity Provider", "default_idp")
223
-
224
- self.promptForString("SSO cookie name", "sso_cookie_name")
225
- self.yesOrNo("Enable seamless login", "seamless_login")
226
- self.yesOrNo("Allow default SSO cookie name", "allow_default_sso_cookie_name")
227
- self.yesOrNo("Use only custom cookie name", "use_only_custom_cookie_name")
228
- self.yesOrNo("Disable LDAP cookie", "disable_ldap_cookie")
229
- self.yesOrNo("Allow custom cache key", "allow_custom_cache_key")
230
-
239
+ if self.showAdvancedOptions:
240
+ self.printH1("Single Sign-On (SSO)")
241
+ self.printDescription([
242
+ "Many aspects of Maximo Application Suite's Single Sign-On (SSO) can be customized:",
243
+ " - Idle session automatic logout timer",
244
+ " - Session, access token, and refresh token timeouts",
245
+ " - Default identity provider (IDP), and seamless login",
246
+ " - Brower cookie properties"
247
+ ])
248
+ if self.yesOrNo("Configure SSO properties"):
249
+ self.promptForInt("Idle session logout timer (seconds)", "idle_timeout")
250
+ self.promptForString("Session timeout (e.g. '12h' for 12 hours)", "idp_session_timeout", validator=TimeoutFormatValidator())
251
+ self.promptForString("Access token timeout (e.g. '30m' for 30 minutes)", "access_token_timeout", validator=TimeoutFormatValidator())
252
+ self.promptForString("Refresh token timeout (e.g. '12h' for 12 hours)", "refresh_token_timeout", validator=TimeoutFormatValidator())
253
+ self.promptForString("Default Identity Provider", "default_idp")
254
+
255
+ self.promptForString("SSO cookie name", "sso_cookie_name")
256
+ self.yesOrNo("Enable seamless login", "seamless_login")
257
+ self.yesOrNo("Allow default SSO cookie name", "allow_default_sso_cookie_name")
258
+ self.yesOrNo("Use only custom cookie name", "use_only_custom_cookie_name")
259
+ self.yesOrNo("Disable LDAP cookie", "disable_ldap_cookie")
260
+ self.yesOrNo("Allow custom cache key", "allow_custom_cache_key")
261
+
262
+ @logMethodCall
231
263
  def configGuidedTour(self):
232
- self.printH1("Enable Guided Tour")
233
- self.printDescription([
234
- "By default, Maximo Application Suite is configured with guided tour, you can disable this if it not required"
235
- ])
236
- if not self.yesOrNo("Enable Guided Tour"):
237
- self.setParam("mas_enable_walkme", "false")
264
+ if self.showAdvancedOptions:
265
+ self.printH1("Enable Guided Tour")
266
+ self.printDescription([
267
+ "By default, Maximo Application Suite is configured with guided tour, you can disable this if it not required"
268
+ ])
269
+ if not self.yesOrNo("Enable Guided Tour"):
270
+ self.setParam("mas_enable_walkme", "false")
238
271
 
272
+ @logMethodCall
239
273
  def configMAS(self):
240
274
  self.printH1("Configure MAS Instance")
241
275
  self.printDescription([
@@ -268,13 +302,18 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
268
302
  self.configSpecialCharacters()
269
303
  self.configGuidedTour()
270
304
 
305
+ @logMethodCall
271
306
  def configCATrust(self) -> None:
272
- self.printH1("Certificate Authority Trust")
273
- self.printDescription([
274
- "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"
275
- ])
276
- self.yesOrNo("Trust default CAs", "mas_trust_default_cas")
307
+ if self.showAdvancedOptions:
308
+ self.printH1("Certificate Authority Trust")
309
+ self.printDescription([
310
+ "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"
311
+ ])
312
+ self.yesOrNo("Trust default CAs", "mas_trust_default_cas")
313
+ else:
314
+ self.setParam("mas_trust_default_cas", True)
277
315
 
316
+ @logMethodCall
278
317
  def configOperationMode(self):
279
318
  self.printH1("Configure Operational Mode")
280
319
  self.printDescription([
@@ -287,62 +326,68 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
287
326
  ])
288
327
  self.operationalMode = self.promptForInt("Operational Mode", default=1)
289
328
 
329
+ @logMethodCall
290
330
  def configAnnotations(self):
291
331
  if self.operationalMode == 2:
292
332
  self.setParam("mas_annotations", "mas.ibm.com/operationalMode=nonproduction")
293
333
 
334
+ @logMethodCall
294
335
  def configSNO(self):
295
336
  if self.isSNO():
296
337
  self.setParam("mongodb_replicas", "1")
297
338
  self.setParam("mongodb_cpu_requests", "500m")
298
339
  self.setParam("mas_app_settings_aio_flag", "false")
299
340
 
341
+ @logMethodCall
300
342
  def configDNSAndCerts(self):
301
- self.printH1("Cluster Ingress Secret Override")
302
- self.printDescription([
303
- "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",
304
- "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"
305
- ])
306
- self.promptForString("Cluster ingress certificate secret name", "ocp_ingress_tls_secret_name", default="")
307
-
308
- self.printH1("Configure Domain & Certificate Management")
309
- configureDomainAndCertMgmt = self.yesOrNo('Configure domain & certificate management')
310
- if configureDomainAndCertMgmt:
311
- configureDomain = self.yesOrNo('Configure custom domain')
312
- if configureDomain:
313
- self.promptForString("MAS top-level domain", "mas_domain")
314
- self.printDescription([
315
- "",
316
- "DNS Integrations:",
317
- " 1. Cloudflare",
318
- " 2. IBM Cloud Internet Services",
319
- " 3. AWS Route 53",
320
- " 4. None (I will set up DNS myself)"
321
- ])
322
-
323
- dnsProvider = self.promptForInt("DNS Provider")
324
-
325
- if dnsProvider == 1:
326
- self.configDNSAndCertsCloudflare()
327
- elif dnsProvider == 2:
328
- self.configDNSAndCertsCIS()
329
- elif dnsProvider == 3:
330
- self.configDNSAndCertsRoute53()
331
- elif dnsProvider == 4:
332
- # Use MAS default self-signed cluster issuer with a custom domain
343
+ if self.showAdvancedOptions:
344
+ self.printH1("Cluster Ingress Secret Override")
345
+ self.printDescription([
346
+ "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",
347
+ "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"
348
+ ])
349
+ self.promptForString("Cluster ingress certificate secret name", "ocp_ingress_tls_secret_name", default="")
350
+
351
+ self.printH1("Configure Domain & Certificate Management")
352
+ configureDomainAndCertMgmt = self.yesOrNo('Configure domain & certificate management')
353
+ if configureDomainAndCertMgmt:
354
+ configureDomain = self.yesOrNo('Configure custom domain')
355
+ if configureDomain:
356
+ self.promptForString("MAS top-level domain", "mas_domain")
357
+ self.printDescription([
358
+ "",
359
+ "DNS Integrations:",
360
+ " 1. Cloudflare",
361
+ " 2. IBM Cloud Internet Services",
362
+ " 3. AWS Route 53",
363
+ " 4. None (I will set up DNS myself)"
364
+ ])
365
+
366
+ dnsProvider = self.promptForInt("DNS Provider")
367
+
368
+ if dnsProvider == 1:
369
+ self.configDNSAndCertsCloudflare()
370
+ elif dnsProvider == 2:
371
+ self.configDNSAndCertsCIS()
372
+ elif dnsProvider == 3:
373
+ self.configDNSAndCertsRoute53()
374
+ elif dnsProvider == 4:
375
+ # Use MAS default self-signed cluster issuer with a custom domain
376
+ self.setParam("dns_provider", "")
377
+ self.setParam("mas_cluster_issuer", "")
378
+ else:
379
+ # Use MAS default self-signed cluster issuer with the default domain
333
380
  self.setParam("dns_provider", "")
381
+ self.setParam("mas_domain", "")
334
382
  self.setParam("mas_cluster_issuer", "")
335
- else:
336
- # Use MAS default self-signed cluster issuer with the default domain
337
- self.setParam("dns_provider", "")
338
- self.setParam("mas_domain", "")
339
- self.setParam("mas_cluster_issuer", "")
340
- self.manualCerts = self.yesOrNo("Configure manual certificates")
341
- self.setParam("mas_manual_cert_mgmt", self.manualCerts)
342
- if self.getParam("mas_manual_cert_mgmt"):
343
- self.manualCertsDir = self.promptForDir("Enter the path containing the manual certificates", mustExist=True)
344
- self.setParam("mas_manual_cert_dir", self.manualCertsDir)
383
+ self.manualCerts = self.yesOrNo("Configure manual certificates")
384
+ self.setParam("mas_manual_cert_mgmt", self.manualCerts)
385
+ if self.getParam("mas_manual_cert_mgmt"):
386
+ self.manualCertsDir = self.promptForDir("Enter the path containing the manual certificates", mustExist=True)
387
+ else:
388
+ self.manualCertsDir = None
345
389
 
390
+ @logMethodCall
346
391
  def configDNSAndCertsCloudflare(self):
347
392
  # User has chosen to set up DNS integration with Cloudflare
348
393
  self.setParam("dns_provider", "cloudflare")
@@ -365,6 +410,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
365
410
  ]
366
411
  self.setParam("mas_cluster_issuer", certIssuerOptions[certIssuer - 1])
367
412
 
413
+ @logMethodCall
368
414
  def configDNSAndCertsCIS(self):
369
415
  self.setParam("dns_provider", "cis")
370
416
  self.promptForString("CIS e-mail", "cis_email")
@@ -386,6 +432,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
386
432
  ]
387
433
  self.setParam("mas_cluster_issuer", certIssuerOptions[certIssuer - 1])
388
434
 
435
+ @logMethodCall
389
436
  def configDNSAndCertsRoute53(self):
390
437
  self.setParam("dns_provider", "route53")
391
438
  self.printDescription([
@@ -410,6 +457,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
410
457
 
411
458
  self.setParam("mas_cluster_issuer", f"{self.getParam('mas_instance_id')}-route53-le-prod")
412
459
 
460
+ @logMethodCall
413
461
  def configApps(self):
414
462
  self.printH1("Application Selection")
415
463
  self.installIoT = self.yesOrNo("Install IoT")
@@ -453,6 +501,11 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
453
501
  if self.installInspection:
454
502
  self.configAppChannel("visualinspection")
455
503
 
504
+ self.installAiBroker = self.yesOrNo("Install AI Broker")
505
+ if self.installAiBroker:
506
+ self.configAppChannel("aibroker")
507
+
508
+ @logMethodCall
456
509
  def configAppChannel(self, appId):
457
510
  versions = self.getCompatibleVersions(self.params["mas_channel"], appId)
458
511
  if len(versions) == 0:
@@ -460,6 +513,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
460
513
  else:
461
514
  self.params[f"mas_app_channel_{appId}"] = versions[0]
462
515
 
516
+ @logMethodCall
463
517
  def configStorageClasses(self):
464
518
  self.printH1("Configure Storage Class Usage")
465
519
  self.printDescription([
@@ -535,11 +589,13 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
535
589
  self.pipelineStorageClass = self.getParam("storage_class_rwx")
536
590
  self.pipelineStorageAccessMode = "ReadWriteMany"
537
591
 
592
+ @logMethodCall
538
593
  def setIoTStorageClasses(self) -> None:
539
594
  if self.installIoT:
540
595
  self.setParam("mas_app_settings_iot_fpl_pvc_storage_class", self.getParam("storage_class_rwo"))
541
596
  self.setParam("mas_app_settings_iot_mqttbroker_pvc_storage_class", self.getParam("storage_class_rwo"))
542
597
 
598
+ @logMethodCall
543
599
  def optimizerSettings(self) -> None:
544
600
  if self.installOptimizer:
545
601
  self.printH1("Configure Maximo Optimizer")
@@ -547,8 +603,9 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
547
603
 
548
604
  self.promptForString("Plan [full/limited]", "mas_app_plan_optimizer", default="full", validator=OptimizerInstallPlanValidator())
549
605
 
606
+ @logMethodCall
550
607
  def predictSettings(self) -> None:
551
- if self.installPredict:
608
+ if self.showAdvancedOptions and self.installPredict:
552
609
  self.printH1("Configure Maximo Predict")
553
610
  self.printDescription([
554
611
  "Predict application supports integration with IBM SPSS and Watson Openscale which are optional services installed on top of IBM Cloud Pak for Data",
@@ -558,6 +615,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
558
615
  self.yesOrNo("Install IBM SPSS Statistics", "cpd_install_spss")
559
616
  self.yesOrNo("Install Watson OpenScale", "cpd_install_openscale")
560
617
 
618
+ @logMethodCall
561
619
  def assistSettings(self) -> None:
562
620
  if self.installAssist:
563
621
  self.printH1("Configure Maximo Assist")
@@ -568,12 +626,43 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
568
626
  self.promptForString("COS Provider [ibm/ocs]", "cos_type")
569
627
  if self.getParam("cos_type") == "ibm":
570
628
  self.promptForString("IBM Cloud API Key", "ibmcloud_apikey", isPassword=True)
571
- self.promptForString("IBM Cloud Resource Group", "cos_resourcegroup")
629
+ self.promptForString("IBM Cloud Resource Group", "ibmcos_resourcegroup")
630
+
631
+ @logMethodCall
632
+ def chooseInstallFlavour(self) -> None:
633
+ self.printH1("Choose Install Mode")
634
+ self.printDescription([
635
+ "There are two flavours of the interactive install to choose from: <u>Simplified</u> and <u>Advanced</u>. The simplified option will present fewer dialogs, but you lose the ability to configure the following aspects of the installation:",
636
+ " - Configure installation namespaces",
637
+ " - Provide pod templates",
638
+ " - Configure Single Sign-On (SSO) settings"
639
+ " - Configure whether to trust well-known certificate authorities by default (defaults to enabled)",
640
+ " - Configure whether the Guided Tour feature is enabled (defaults to enabled)",
641
+ " - Configure whether special characters are allowed in usernames and userids (defaults to disabled)",
642
+ " - Configure a custom domain, DNS integrations, and manual certificates",
643
+ " - Customize Maximo Manage database settings (schema, tablespace, indexspace)",
644
+ " - Customize Maximo Manage server bundle configuration (defaults to \"all\" configuration)",
645
+ " - Enable optional Maximo Manage integration Cognos Analytics and Watson Studio Local",
646
+ " - Enable optional Maximo Predict integration with SPSS and Watson OpenScale",
647
+ " - Enable optional IBM Turbonomic integration",
648
+ " - Customize Db2 node affinity and tolerations, memory, cpu, and storage settings (when using the IBM Db2 Universal Operator)",
649
+ " - Choose alternative Apache Kafka providers (default to Strimzi)",
650
+ " - Customize Grafana storage settings"
651
+ ])
652
+ self.showAdvancedOptions = self.yesOrNo("Show advanced installation options")
572
653
 
573
- def interactiveMode(self) -> None:
654
+ @logMethodCall
655
+ def interactiveMode(self, simplified: bool, advanced: bool) -> None:
574
656
  # Interactive mode
575
657
  self.interactiveMode = True
576
658
 
659
+ if simplified:
660
+ self.showAdvancedOptions = False
661
+ elif advanced:
662
+ self.showAdvancedOptions = True
663
+ else:
664
+ self.chooseInstallFlavour()
665
+
577
666
  # Catalog
578
667
  self.configCatalog()
579
668
  if not self.devMode:
@@ -600,9 +689,10 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
600
689
  self.optimizerSettings()
601
690
  self.predictSettings()
602
691
  self.assistSettings()
692
+ self.aibrokerSettings()
603
693
 
604
694
  # Dependencies
605
- self.configMongoDb() # Will only do anything if IoT or Manage have been selected for install
695
+ self.configMongoDb()
606
696
  self.configDb2()
607
697
  self.configKafka() # Will only do anything if IoT has been selected for install
608
698
 
@@ -612,11 +702,15 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
612
702
  # TODO: Support ECK integration via the interactive install mode
613
703
  # TODO: Support MAS superuser username/password via the interactive install mode
614
704
 
705
+ @logMethodCall
615
706
  def nonInteractiveMode(self) -> None:
616
- # Non-interactive mode
617
707
  self.interactiveMode = False
618
708
 
619
709
  # Set defaults
710
+ # ---------------------------------------------------------------------
711
+ # Unless a config file named "mongodb-system.yaml" is provided via the additional configs mechanism we will be installing a new MongoDb instance
712
+ self.setParam("mongodb_action", "install")
713
+
620
714
  self.storageClassProvider = "custom"
621
715
  self.installAssist = False
622
716
  self.installIoT = False
@@ -625,6 +719,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
625
719
  self.installPredict = False
626
720
  self.installInspection = False
627
721
  self.installOptimizer = False
722
+ self.installAiBroker = False
628
723
  self.deployCP4D = False
629
724
  self.db2SetAffinity = False
630
725
  self.db2SetTolerations = False
@@ -642,128 +737,6 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
642
737
 
643
738
  self.configGrafana()
644
739
 
645
- requiredParams = [
646
- # MAS
647
- "mas_catalog_version",
648
- "mas_channel",
649
- "mas_instance_id",
650
- "mas_workspace_id",
651
- "mas_workspace_name",
652
- # Storage classes
653
- "storage_class_rwo",
654
- "storage_class_rwx",
655
- # Entitlement
656
- "ibm_entitlement_key",
657
- # DRO
658
- "uds_contact_email",
659
- "uds_contact_firstname",
660
- "uds_contact_lastname"
661
- ]
662
- optionalParams = [
663
- # MAS
664
- "mas_catalog_digest",
665
- "mas_superuser_username",
666
- "mas_superuser_password",
667
- "mas_trust_default_cas",
668
- "mas_app_settings_server_bundles_size",
669
- "mas_app_settings_default_jms",
670
- "mas_app_settings_persistent_volumes_flag",
671
- "mas_appws_bindings_jdbc_manage",
672
- "mas_app_settings_demodata",
673
- "mas_appws_components",
674
- "mas_app_settings_customization_archive_name",
675
- "mas_app_settings_customization_archive_url",
676
- "mas_app_settings_customization_archive_username",
677
- "mas_app_settings_customization_archive_password",
678
- "mas_app_settings_tablespace",
679
- "mas_app_settings_indexspace",
680
- "mas_app_settings_db2_schema",
681
- "mas_app_settings_crypto_key",
682
- "mas_app_settings_cryptox_key",
683
- "mas_app_settings_old_crypto_key",
684
- "mas_app_settings_old_cryptox_key",
685
- "mas_app_settings_override_encryption_secrets_flag",
686
- "mas_app_settings_base_lang",
687
- "mas_app_settings_secondary_langs",
688
- "mas_app_settings_server_timezone",
689
- "ocp_ingress_tls_secret_name",
690
- # DRO
691
- "dro_namespace",
692
- # MongoDb
693
- "mongodb_namespace",
694
- # Db2
695
- "db2_action_system",
696
- "db2_action_manage",
697
- "db2_type",
698
- "db2_timezone",
699
- "db2_namespace",
700
- "db2_channel",
701
- "db2_affinity_key",
702
- "db2_affinity_value",
703
- "db2_tolerate_key",
704
- "db2_tolerate_value",
705
- "db2_tolerate_effect",
706
- "db2_cpu_requests",
707
- "db2_cpu_limits",
708
- "db2_memory_requests",
709
- "db2_memory_limits",
710
- "db2_backup_storage_size",
711
- "db2_data_storage_size",
712
- "db2_logs_storage_size",
713
- "db2_meta_storage_size",
714
- "db2_temp_storage_size",
715
- # CP4D
716
- "cpd_product_version",
717
- "cpd_install_cognos",
718
- "cpd_install_openscale",
719
- "cpd_install_spss",
720
- # Kafka
721
- "kafka_namespace",
722
- "kafka_version",
723
- "aws_msk_instance_type",
724
- "aws_msk_instance_number",
725
- "aws_msk_volume_size",
726
- "aws_msk_cidr_az1",
727
- "aws_msk_cidr_az2",
728
- "aws_msk_cidr_az3",
729
- "aws_msk_egress_cidr",
730
- "aws_msk_ingress_cidr",
731
- "eventstreams_resource_group",
732
- "eventstreams_instance_name",
733
- "eventstreams_instance_location",
734
- # COS
735
- "cos_type",
736
- "cos_resourcegroup",
737
- # ECK
738
- "eck_action",
739
- "eck_enable_logstash",
740
- "eck_remote_es_hosts",
741
- "eck_remote_es_username",
742
- "eck_remote_es_password",
743
- # Turbonomic
744
- "turbonomic_target_name",
745
- "turbonomic_server_url",
746
- "turbonomic_server_version",
747
- "turbonomic_username",
748
- "turbonomic_password",
749
- # Cloud Providers
750
- "ibmcloud_apikey",
751
- "aws_region",
752
- "aws_access_key_id",
753
- "secret_access_key",
754
- "aws_vpc_id",
755
- # Dev Mode
756
- "artifactory_username",
757
- "artifactory_token",
758
- # TODO: The way arcgis has been implemented needs to be fixed
759
- "install_arcgis",
760
- "mas_arcgis_channel",
761
- # Guided Tour
762
- "mas_enable_walkme",
763
- # Special chars
764
- "mas_special_characters"
765
- ]
766
-
767
740
  for key, value in vars(self.args).items():
768
741
  # These fields we just pass straight through to the parameters and fail if they are not set
769
742
  if key in requiredParams:
@@ -800,6 +773,10 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
800
773
 
801
774
  elif key == "additional_configs":
802
775
  self.localConfigDir = value
776
+ # If there is a file named mongodb-system.yaml we will use this as a BYO MongoDB datasource
777
+ if path.exists(path.join(self.localConfigDir, "mongodb-system.yaml")):
778
+ self.setParam("mongodb_action", "byo")
779
+ self.setParam("sls_mongodb_cfg_file", "/workspace/additional-configs/mongodb-system.yaml")
803
780
 
804
781
  elif key == "pod_templates":
805
782
  # For the named configurations we will convert into the path
@@ -808,37 +785,44 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
808
785
  else:
809
786
  self.setParam("mas_pod_templates_dir", value)
810
787
 
788
+ # We check for both None and "" values for the application channel parameters
789
+ # value = None means the parameter wasn't set at all
790
+ # value = "" means the paramerter was explicitly set to "don't install this application"
811
791
  elif key == "assist_channel":
812
- if value is not None:
792
+ if value is not None and value != "":
813
793
  self.setParam("mas_app_channel_assist", value)
814
794
  self.installAssist = True
815
795
  elif key == "iot_channel":
816
- if value is not None:
796
+ if value is not None and value != "":
817
797
  self.setParam("mas_app_channel_iot", value)
818
798
  self.installIoT = True
819
799
  elif key == "monitor_channel":
820
- if value is not None:
800
+ if value is not None and value != "":
821
801
  self.setParam("mas_app_channel_monitor", value)
822
802
  self.installMonitor = True
823
803
  elif key == "manage_channel":
824
- if value is not None:
804
+ if value is not None and value != "":
825
805
  self.setParam("mas_app_channel_manage", value)
826
806
  self.installManage = True
827
807
  elif key == "predict_channel":
828
- if value is not None:
808
+ if value is not None and value != "":
829
809
  self.setParam("mas_app_channel_predict", value)
830
810
  self.installPredict = True
831
811
  self.deployCP4D = True
832
812
  elif key == "visualinspection_channel":
833
- if value is not None:
813
+ if value is not None and value != "":
834
814
  self.setParam("mas_app_channel_visualinspection", value)
835
815
  self.installInspection = True
816
+ elif key == "aibroker_channel":
817
+ if value is not None and value != "":
818
+ self.setParam("mas_app_channel_aibroker", value)
819
+ self.installAiBroker = True
836
820
  elif key == "optimizer_channel":
837
- if value is not None:
821
+ if value is not None and value != "":
838
822
  self.setParam("mas_app_channel_optimizer", value)
839
823
  self.installOptimizer = True
840
824
  elif key == "optimizer_plan":
841
- if value is not None:
825
+ if value is not None and value != "":
842
826
  self.setParam("mas_app_plan_optimizer", value)
843
827
 
844
828
  # Manage advanced settings that need extra processing
@@ -880,15 +864,16 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
880
864
  self.fatalError(f"Unsupported format for {key} ({value}). Expected string:int:int:boolean")
881
865
 
882
866
  # Arguments that we don't need to do anything with
883
- elif key in ["accept_license", "dev_mode", "skip_pre_check", "skip_grafana_install", "no_confirm", "no_wait_for_pvc", "help"]:
867
+ elif key in ["accept_license", "dev_mode", "skip_pre_check", "skip_grafana_install", "no_confirm", "no_wait_for_pvc", "help", "advanced", "simplified"]:
884
868
  pass
885
869
 
886
870
  elif key == "manual_certificates":
887
871
  if value is not None:
888
872
  self.setParam("mas_manual_cert_mgmt", True)
889
- self.setParam("mas_manual_cert_dir", value)
873
+ self.manualCertsDir = value
890
874
  else:
891
875
  self.setParam("mas_manual_cert_mgmt", False)
876
+ self.manualCertsDir = None
892
877
 
893
878
  # Fail if there's any arguments we don't know how to handle
894
879
  else:
@@ -900,6 +885,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
900
885
  self.validateCatalogSource()
901
886
  self.licensePrompt()
902
887
 
888
+ @logMethodCall
903
889
  def install(self, argv):
904
890
  """
905
891
  Install MAS instance
@@ -926,140 +912,24 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
926
912
  if args.skip_pre_check:
927
913
  self.setParam("skip_pre_check", "true")
928
914
 
929
- self.installOptions = [
930
- {
931
- "#": 1,
932
- "catalog": "v9-241003-amd64",
933
- "release": "9.0.x",
934
- "core": "9.0.3",
935
- "assist": "9.0.2",
936
- "iot": "9.0.3",
937
- "manage": "9.0.3",
938
- "monitor": "9.0.3",
939
- "optimizer": "9.0.3",
940
- "predict": "9.0.2",
941
- "inspection": "9.0.3"
942
- },
943
- {
944
- "#": 2,
945
- "catalog": "v9-241003-amd64",
946
- "release": "8.11.x",
947
- "core": "8.11.15",
948
- "assist": "8.8.6",
949
- "iot": "8.8.13",
950
- "manage": "8.7.12",
951
- "monitor": "8.11.11",
952
- "optimizer": "8.5.9",
953
- "predict": "8.9.5",
954
- "inspection": "8.9.6"
955
- },
956
- {
957
- "#": 3,
958
- "catalog": "v9-241003-amd64",
959
- "release": "8.10.x",
960
- "core": "8.10.18",
961
- "assist": "8.7.7",
962
- "iot": "8.7.17",
963
- "manage": "8.6.18",
964
- "monitor": "8.10.14",
965
- "optimizer": "8.4.10",
966
- "predict": "8.8.3",
967
- "inspection": "8.8.4"
968
- },
969
- {
970
- "#": 4,
971
- "catalog": "v9-240827-amd64",
972
- "release": "9.0.x",
973
- "core": "9.0.2",
974
- "assist": "9.0.2",
975
- "iot": "9.0.2",
976
- "manage": "9.0.2",
977
- "monitor": "9.0.2",
978
- "optimizer": "9.0.2",
979
- "predict": "9.0.1",
980
- "inspection": "9.0.2"
981
- },
982
- {
983
- "#": 5,
984
- "catalog": "v9-240827-amd64",
985
- "release": "8.11.x",
986
- "core": "8.11.14",
987
- "assist": "8.8.6",
988
- "iot": "8.8.12",
989
- "manage": "8.7.11",
990
- "monitor": "8.11.10",
991
- "optimizer": "8.5.8",
992
- "predict": "8.9.3",
993
- "inspection": "8.9.5"
994
- },
995
- {
996
- "#": 6,
997
- "catalog": "v9-240827-amd64",
998
- "release": "8.10.x",
999
- "core": "8.10.17",
1000
- "assist": "8.7.7",
1001
- "iot": "8.7.16",
1002
- "manage": "8.6.17",
1003
- "monitor": "8.10.13",
1004
- "optimizer": "8.4.9",
1005
- "predict": "8.8.3",
1006
- "inspection": "8.8.4"
1007
- },
1008
- {
1009
- "#": 7,
1010
- "catalog": "v9-240730-amd64",
1011
- "release": "9.0.x",
1012
- "core": "9.0.1",
1013
- "assist": "9.0.1",
1014
- "iot": "9.0.1",
1015
- "manage": "9.0.1",
1016
- "monitor": "9.0.1",
1017
- "optimizer": "9.0.1",
1018
- "predict": "9.0.0",
1019
- "inspection": "9.0.0"
1020
- },
1021
- {
1022
- "#": 8,
1023
- "catalog": "v9-240730-amd64",
1024
- "release": "8.11.x",
1025
- "core": "8.11.13",
1026
- "assist": "8.8.5",
1027
- "iot": "8.8.11",
1028
- "manage": "8.7.10",
1029
- "monitor": "8.11.9",
1030
- "optimizer": "8.5.7",
1031
- "predict": "8.9.3",
1032
- "inspection": "8.9.4"
1033
- },
1034
- {
1035
- "#": 9,
1036
- "catalog": "v9-240730-amd64",
1037
- "release": "8.10.x",
1038
- "core": "8.10.16",
1039
- "assist": "8.7.6",
1040
- "iot": "8.7.15",
1041
- "manage": "8.6.16",
1042
- "monitor": "8.10.12",
1043
- "optimizer": "8.4.8",
1044
- "predict": "8.8.3",
1045
- "inspection": "8.8.4"
1046
- }
1047
- ]
1048
-
1049
915
  if instanceId is None:
1050
916
  self.printH1("Set Target OpenShift Cluster")
1051
917
  # Connect to the target cluster
1052
918
  self.connect()
1053
919
  else:
1054
920
  logger.debug("MAS instance ID is set, so we assume already connected to the desired OCP")
921
+ self.lookupTargetArchitecture()
1055
922
 
1056
923
  if self.dynamicClient is None:
1057
924
  print_formatted_text(HTML("<Red>Error: The Kubernetes dynamic Client is not available. See log file for details</Red>"))
1058
925
  exit(1)
1059
926
 
927
+ # Configure the installOptions for the appropriate architecture
928
+ self.installOptions = catalogChoices[self.architecture]
929
+
1060
930
  # Basic settings before the user provides any input
1061
931
  self.configICR()
1062
- self.configCertManager()
932
+ self.configCertManager() # TODO: I think this is redundant, we should look to remove this and the appropriate params in the install pipeline
1063
933
  self.deployCP4D = False
1064
934
 
1065
935
  # UDS install has not been supported since the January 2024 catalog update
@@ -1067,7 +937,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
1067
937
 
1068
938
  # User must either provide the configuration via numerous command line arguments, or the interactive prompts
1069
939
  if instanceId is None:
1070
- self.interactiveMode()
940
+ self.interactiveMode(simplified=args.simplified, advanced=args.advanced)
1071
941
  else:
1072
942
  self.nonInteractiveMode()
1073
943
 
@@ -1161,6 +1031,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
1161
1031
  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")
1162
1032
  print()
1163
1033
 
1034
+ @logMethodCall
1164
1035
  def setupApprovals(self, namespace: str) -> None:
1165
1036
  """
1166
1037
  Ensure the supported approval configmaps are in the expected state for the start of the run: