mas-cli 12.0.0__py3-none-any.whl → 12.27.0__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (33) hide show
  1. mas/cli/__init__.py +1 -1
  2. mas/cli/aiservice/install/__init__.py +11 -0
  3. mas/cli/aiservice/install/app.py +810 -0
  4. mas/cli/aiservice/install/argBuilder.py +232 -0
  5. mas/cli/aiservice/install/argParser.py +742 -0
  6. mas/cli/aiservice/install/params.py +120 -0
  7. mas/cli/aiservice/install/summarizer.py +193 -0
  8. mas/cli/cli.py +36 -9
  9. mas/cli/gencfg.py +23 -0
  10. mas/cli/install/app.py +295 -85
  11. mas/cli/install/argBuilder.py +92 -14
  12. mas/cli/install/argParser.py +200 -147
  13. mas/cli/install/catalogs.py +11 -6
  14. mas/cli/install/params.py +32 -6
  15. mas/cli/install/settings/additionalConfigs.py +18 -1
  16. mas/cli/install/settings/db2Settings.py +121 -72
  17. mas/cli/install/settings/kafkaSettings.py +2 -2
  18. mas/cli/install/settings/manageSettings.py +154 -159
  19. mas/cli/install/settings/mongodbSettings.py +1 -1
  20. mas/cli/install/settings/turbonomicSettings.py +1 -3
  21. mas/cli/install/summarizer.py +85 -68
  22. mas/cli/templates/facilities-configs.yml.j2 +25 -0
  23. mas/cli/templates/ibm-mas-tekton.yaml +16683 -7870
  24. mas/cli/update/app.py +43 -9
  25. mas/cli/upgrade/app.py +52 -20
  26. mas/cli/upgrade/argParser.py +7 -0
  27. mas/cli/upgrade/settings/__init__.py +19 -0
  28. mas/cli/validators.py +13 -0
  29. {mas_cli-12.0.0.data → mas_cli-12.27.0.data}/scripts/mas-cli +5 -1
  30. {mas_cli-12.0.0.dist-info → mas_cli-12.27.0.dist-info}/METADATA +12 -3
  31. {mas_cli-12.0.0.dist-info → mas_cli-12.27.0.dist-info}/RECORD +33 -25
  32. {mas_cli-12.0.0.dist-info → mas_cli-12.27.0.dist-info}/WHEEL +1 -1
  33. {mas_cli-12.0.0.dist-info → mas_cli-12.27.0.dist-info}/top_level.txt +0 -0
mas/cli/install/app.py CHANGED
@@ -40,11 +40,17 @@ from mas.cli.validators import (
40
40
  WorkspaceNameFormatValidator,
41
41
  TimeoutFormatValidator,
42
42
  StorageClassValidator,
43
+ JsonValidator,
43
44
  OptimizerInstallPlanValidator
44
45
  )
45
46
 
46
47
  from mas.devops.ocp import createNamespace, getStorageClasses
47
- from mas.devops.mas import getCurrentCatalog, getDefaultStorageClasses
48
+ from mas.devops.mas import (
49
+ getCurrentCatalog,
50
+ getDefaultStorageClasses,
51
+ isVersionEqualOrAfter
52
+ )
53
+ from mas.devops.sls import findSLSByNamespace
48
54
  from mas.devops.data import getCatalog
49
55
  from mas.devops.tekton import (
50
56
  installOpenShiftPipelines,
@@ -158,47 +164,69 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
158
164
  year = date[:2]
159
165
  return f" - {monthName} 20{year} Update\n <Orange><u>https://ibm-mas.github.io/cli/catalogs/{name}</u></Orange>"
160
166
 
161
- def formatRelease(self, release: str) -> str:
162
- return f"{release} ... {self.catalogReleases[release]['core']}"
163
-
164
167
  @logMethodCall
165
168
  def processCatalogChoice(self) -> list:
166
169
  self.catalogDigest = self.chosenCatalog["catalog_digest"]
167
- self.catalogCp4dVersion = self.chosenCatalog["cpd_product_version_default"]
168
170
  self.catalogMongoDbVersion = self.chosenCatalog["mongo_extras_version_default"]
171
+ if self.architecture != "s390x" and self.architecture != "ppc64le":
172
+ self.catalogCp4dVersion = self.chosenCatalog["cpd_product_version_default"]
173
+
174
+ applications = {
175
+ "Core": "mas_core_version",
176
+ "Manage": "mas_manage_version",
177
+ "IoT": "mas_iot_version",
178
+ "Monitor": "mas_monitor_version",
179
+ "Assist": "mas_assist_version",
180
+ "Optimizer": "mas_optimizer_version",
181
+ "Predict": "mas_predict_version",
182
+ "Inspection": "mas_visualinspection_version",
183
+ "Facilities": "mas_facilities_version",
184
+ }
185
+ else:
186
+ applications = {
187
+ "Core": "mas_core_version",
188
+ "Manage": "mas_manage_version",
189
+ }
169
190
 
170
- self.catalogReleases = []
191
+ self.catalogReleases = {}
171
192
  self.catalogTable = []
172
193
 
173
- applications = {
174
- "Core": "mas_core_version",
175
- "Manage": "mas_manage_version",
176
- "IoT": "mas_iot_version",
177
- "Monitor": "mas_monitor_version",
178
- "Assist": "mas_assist_version",
179
- "Optimizer": "mas_optimizer_version",
180
- "Predict": "mas_predict_version",
181
- "Inspection": "mas_visualinspection_version",
182
- }
183
-
184
194
  # Dynamically fetch the channels from the chosen catalog
185
195
  # based on mas core
186
196
  for channel in self.chosenCatalog["mas_core_version"]:
187
- self.catalogReleases.append(channel)
197
+ # {"9.1-feature": "9.1.x-feature"}
198
+ self.catalogReleases.update({channel.replace('.x', ''): channel})
188
199
 
189
200
  # Generate catalogTable
190
201
  for application, key in applications.items():
191
- self.catalogTable.append({"": application} | self.chosenCatalog[key])
202
+ # Add 9.1-feature channel based off 9.0 to those apps that have not onboarded yet
203
+ if key in self.chosenCatalog:
204
+ tempChosenCatalog = self.chosenCatalog[key].copy()
205
+ if '9.1.x-feature' not in tempChosenCatalog:
206
+ tempChosenCatalog.update({"9.1.x-feature": tempChosenCatalog["9.0.x"]})
207
+
208
+ self.catalogTable.append({"": application} | {key.replace(".x", ""): value for key, value in sorted(tempChosenCatalog.items(), reverse=True)})
209
+
210
+ if self.architecture == "s390x" or self.architecture == "ppc64le":
211
+ summary = [
212
+ "",
213
+ "<u>Catalog Details</u>",
214
+ f"Catalog Image: icr.io/cpopen/ibm-maximo-operator-catalog:{self.getParam('mas_catalog_version')}",
215
+ f"Catalog Digest: {self.catalogDigest}",
216
+ f"MAS Releases: {', '.join(sorted(self.catalogReleases, reverse=True))}",
217
+ f"MongoDb: {self.catalogMongoDbVersion}",
218
+ ]
219
+ else:
220
+ summary = [
221
+ "",
222
+ "<u>Catalog Details</u>",
223
+ f"Catalog Image: icr.io/cpopen/ibm-maximo-operator-catalog:{self.getParam('mas_catalog_version')}",
224
+ f"Catalog Digest: {self.catalogDigest}",
225
+ f"MAS Releases: {', '.join(sorted(self.catalogReleases, reverse=True))}",
226
+ f"Cloud Pak for Data: {self.catalogCp4dVersion}",
227
+ f"MongoDb: {self.catalogMongoDbVersion}",
228
+ ]
192
229
 
193
- summary = [
194
- "",
195
- "<u>Catalog Details</u>",
196
- f"Catalog Image: icr.io/cpopen/ibm-maximo-operator-catalog:{self.getParam('mas_catalog_version')}",
197
- f"Catalog Digest: {self.catalogDigest}",
198
- f"MAS Releases: {', '.join(self.catalogReleases)}",
199
- f"Cloud Pak for Data: {self.catalogCp4dVersion}",
200
- f"MongoDb: {self.catalogMongoDbVersion}",
201
- ]
202
230
  return summary
203
231
 
204
232
  @logMethodCall
@@ -237,28 +265,64 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
237
265
  self.printDescription(catalogSummary)
238
266
  self.printDescription([
239
267
  "",
240
- "Multiple releases of Maximo Application Suite are available, each is supported under IBM's standard 3+1+3 support model.",
241
- "Choose the release of IBM Maximo Application Suite that you want to use for this installation from the table below:",
268
+ "Two types of release are available:",
269
+ " - GA releases of Maximo Application Suite are supported under IBM's standard 3+1+3 support lifecycle policy.",
270
+ " - 'Feature' releases allow early access to new features for evaluation in non-production environments and are only supported through to the next GA release.",
242
271
  ""
243
272
  ])
244
273
 
245
274
  print(tabulate(self.catalogTable, headers="keys", tablefmt="simple_grid"))
246
275
 
247
- releaseCompleter = WordCompleter(self.catalogReleases)
276
+ releaseCompleter = WordCompleter(sorted(self.catalogReleases, reverse=True))
248
277
  releaseSelection = self.promptForString("Select release", completer=releaseCompleter)
249
278
 
250
- self.setParam("mas_channel", releaseSelection)
279
+ self.setParam("mas_channel", self.catalogReleases[releaseSelection])
251
280
 
252
281
  @logMethodCall
253
282
  def configSLS(self) -> None:
254
- self.printH1("Configure Product License")
283
+ self.printH1("Configure AppPoint Licensing")
284
+ self.printDescription(
285
+ [
286
+ "By default the MAS instance will be configured to use a cluster-shared License, this provides a shared pool of AppPoints available to all MAS instances on the cluster.",
287
+ "",
288
+ ]
289
+ )
290
+
291
+ self.slsMode = 1
292
+ self.slsLicenseFileLocal = None
293
+
294
+ if self.showAdvancedOptions:
295
+ self.printDescription(
296
+ [
297
+ "Alternatively you may choose to install using a dedicated license only available to this MAS instance.",
298
+ " 1. Install MAS with Cluster-Shared License (AppPoints)",
299
+ " 2. Install MAS with Dedicated License (AppPoints)",
300
+ ]
301
+ )
302
+ self.slsMode = self.promptForInt("SLS Mode", default=1)
303
+
304
+ if self.slsMode not in [1, 2]:
305
+ self.fatalError(f"Invalid selection: {self.slsMode}")
306
+
307
+ if not (self.slsMode == 2 and not self.getParam("sls_namespace")):
308
+ sls_namespace = "ibm-sls" if self.slsMode == 1 else self.getParam("sls_namespace")
309
+ if findSLSByNamespace(sls_namespace, dynClient=self.dynamicClient):
310
+ print_formatted_text(HTML(f"<MediumSeaGreen>SLS auto-detected: {sls_namespace}</MediumSeaGreen>"))
311
+ print()
312
+ if not self.yesOrNo("Upload/Replace the license file"):
313
+ self.setParam("sls_action", "gencfg")
314
+ return
315
+
255
316
  self.slsLicenseFileLocal = self.promptForFile("License file", mustExist=True, envVar="SLS_LICENSE_FILE_LOCAL")
317
+ self.setParam("sls_action", "install")
318
+
319
+ @logMethodCall
320
+ def configDRO(self) -> None:
256
321
  self.promptForString("Contact e-mail address", "uds_contact_email")
257
322
  self.promptForString("Contact first name", "uds_contact_firstname")
258
323
  self.promptForString("Contact last name", "uds_contact_lastname")
259
324
 
260
325
  if self.showAdvancedOptions:
261
- self.promptForString("IBM Suite License Services (SLS) Namespace", "sls_namespace", default="ibm-sls")
262
326
  self.promptForString("IBM Data Reporter Operator (DRO) Namespace", "dro_namespace", default="redhat-marketplace")
263
327
 
264
328
  @logMethodCall
@@ -269,8 +333,8 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
269
333
 
270
334
  @logMethodCall
271
335
  def configGrafana(self) -> None:
272
- if self.architecture == "s390x":
273
- # We are not supporting Grafana on s390x at the moment
336
+ if self.architecture == "s390x" or self.architecture == "ppc64le":
337
+ # We are not supporting Grafana on s390x /ppc64le at the moment
274
338
  self.setParam("grafana_action", "none")
275
339
  else:
276
340
  try:
@@ -312,7 +376,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
312
376
  self.printDescription([
313
377
  f"Unknown catalog {self.getParam('mas_catalog_version')}, please manually select the version of Cloud Pak for Data to use"
314
378
  ])
315
- self.promptForString("Cloud Pak for Data product version", "cpd_product_version", default="4.8.0")
379
+ self.promptForString("Cloud Pak for Data product version", "cpd_product_version", default="5.1.3")
316
380
  logger.debug(f"Using user-provided (prompt) CP4D product version: {self.getParam('cpd_product_version')}")
317
381
  else:
318
382
  logger.debug(f"Using user-provided (flags) CP4D product version: {self.getParam('cpd_product_version')}")
@@ -379,6 +443,9 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
379
443
  ])
380
444
  self.promptForString("Workspace name", "mas_workspace_name", validator=WorkspaceNameFormatValidator())
381
445
 
446
+ if self.slsMode == 2 and not self.getParam("sls_namespace"):
447
+ self.setParam("sls_namespace", f"mas-{self.getParam('mas_instance_id')}-sls")
448
+
382
449
  self.configOperationMode()
383
450
  self.configCATrust()
384
451
  self.configDNSAndCerts()
@@ -555,13 +622,27 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
555
622
  if self.installMonitor:
556
623
  self.configAppChannel("monitor")
557
624
 
558
- self.installManage = self.yesOrNo("Install Manage")
625
+ self.manageAppName = "Manage"
626
+ self.isManageFoundation = False
627
+ self.installManage = self.yesOrNo(f"Install {self.manageAppName}")
628
+
629
+ # If the selection was to not install manage but we are in mas_channel 9.1 or later, we need to set self.isManageFoundation to True
630
+ # Also, we need to force self.installManage to be True because Manage must always be installed in MAS 9.1 or later
631
+ if not self.installManage:
632
+ if not self.getParam("mas_channel").startswith("8.") and not self.getParam("mas_channel").startswith("9.0"):
633
+ self.installManage = True
634
+ self.isManageFoundation = True
635
+ self.setParam("is_full_manage", "false")
636
+ self.setParam("mas_app_settings_aio_flag", "false")
637
+ self.manageAppName = "Manage foundation"
638
+ self.printDescription([f"{self.manageAppName} installs the following capabilities: User, Security groups, Application configurator and Mobile configurator."])
639
+ else:
640
+ self.setParam("is_full_manage", "true")
559
641
 
560
642
  if self.installManage:
561
643
  self.configAppChannel("manage")
562
644
 
563
- # 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
564
- if self.installIoT and self.installManage and self.getParam("mas_channel") != "8.10.x":
645
+ if self.installIoT and self.installManage:
565
646
  self.installPredict = self.yesOrNo("Install Predict")
566
647
  else:
567
648
  self.installPredict = False
@@ -570,7 +651,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
570
651
  self.configAppChannel("predict")
571
652
 
572
653
  # 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
573
- if not self.getParam("mas_channel").startswith("8."):
654
+ if isVersionEqualOrAfter('9.0.0', self.getParam("mas_channel")):
574
655
  self.installAssist = self.yesOrNo("Install Assist")
575
656
  if self.installAssist:
576
657
  self.configAppChannel("assist")
@@ -585,9 +666,12 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
585
666
  if self.installInspection:
586
667
  self.configAppChannel("visualinspection")
587
668
 
588
- self.installAiBroker = self.yesOrNo("Install AI Broker")
589
- if self.installAiBroker:
590
- self.configAppChannel("aibroker")
669
+ if isVersionEqualOrAfter('9.1.0', self.getParam("mas_channel")) and self.getParam("mas_channel") != '9.1.x-feature':
670
+ self.installFacilities = self.yesOrNo("Install Real Estate and Facilities")
671
+ if self.installFacilities:
672
+ self.configAppChannel("facilities")
673
+ else:
674
+ self.installFacilities = False
591
675
 
592
676
  @logMethodCall
593
677
  def configAppChannel(self, appId):
@@ -651,21 +735,23 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
651
735
  def optimizerSettings(self) -> None:
652
736
  if self.installOptimizer:
653
737
  self.printH1("Configure Maximo Optimizer")
654
- self.printDescription(["Customize your Optimizer installation, 'full' and 'limited' install plans are available, refer to the product documentation for more information"])
655
-
656
- self.promptForString("Plan [full/limited]", "mas_app_plan_optimizer", default="full", validator=OptimizerInstallPlanValidator())
738
+ if self.isSNO():
739
+ self.printDescription(["Using Optimizer 'limited' plan as it is being installed in a single node cluster"])
740
+ self.setParam("mas_app_plan_optimizer", "limited")
741
+ else:
742
+ self.printDescription(["Customize your Optimizer installation, 'full' and 'limited' install plans are available, refer to the product documentation for more information"])
743
+ self.promptForString("Plan [full/limited]", "mas_app_plan_optimizer", default="full", validator=OptimizerInstallPlanValidator())
657
744
 
658
745
  @logMethodCall
659
746
  def predictSettings(self) -> None:
660
747
  if self.showAdvancedOptions and self.installPredict:
661
748
  self.printH1("Configure Maximo Predict")
662
749
  self.printDescription([
663
- "Predict application supports integration with IBM SPSS and Watson Openscale which are optional services installed on top of IBM Cloud Pak for Data",
750
+ "Predict application supports integration with IBM SPSS which is an optional service installed on top of IBM Cloud Pak for Data",
664
751
  "Unless requested these will not be installed"
665
752
  ])
666
753
  self.configCP4D()
667
754
  self.yesOrNo("Install IBM SPSS Statistics", "cpd_install_spss")
668
- self.yesOrNo("Install Watson OpenScale", "cpd_install_openscale")
669
755
 
670
756
  @logMethodCall
671
757
  def assistSettings(self) -> None:
@@ -680,6 +766,81 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
680
766
  self.promptForString("IBM Cloud API Key", "cos_apikey", isPassword=True)
681
767
  self.promptForString("IBM Cloud Resource Group", "cos_resourcegroup")
682
768
 
769
+ @logMethodCall
770
+ def facilitiesSettings(self) -> None:
771
+ if self.installFacilities:
772
+ self.printH1("Configure Maximo Real Estate and Facilities")
773
+ self.printDescription([
774
+ "Real Estate and Facilities custom configurations"
775
+ ])
776
+ self.printDescription([
777
+ "Maximo Real Estate and Facilities Size:",
778
+ " 1. Small",
779
+ " 2. Medium",
780
+ " 3. Large"
781
+ ])
782
+ self.promptForListSelect("Select the size:", ["small", "medium", "large"], "mas_ws_facilities_size")
783
+
784
+ if self.showAdvancedOptions:
785
+ self.printH2("Maximo Real Estate and Facilities Settings - Advanced")
786
+ self.printDescription([
787
+ "Advanced configurations for Real Estate and Facilities are added through an additional file called facilities-configs.yaml"
788
+ ])
789
+ if self.yesOrNo("Supply extra XML tags for Real Estate and Facilities server.xml"):
790
+ self.promptForString("Real Estate and Facilities Liberty Extension Secret Name", "mas_ws_facilities_liberty_extension_XML")
791
+ if self.yesOrNo("Supply custom AES Encryption Password"):
792
+ self.promptForString("Real Estate and Facilities AES Vault Secret Name", "mas_ws_facilities_vault_secret")
793
+
794
+ self.promptForString("Set Real Estate and Facilities Routes Timeout:", "mas_ws_facilities_routes_timeout", default="600s")
795
+ self.promptForInt("Set Facilities maximum connection poll size:", "mas_ws_facilities_db_maxconnpoolsize", default=200)
796
+
797
+ self.printDescription(["Real Estate and Facilities Persistent Volume Storage Configuration"])
798
+ defaultStorageClasses = getDefaultStorageClasses(self.dynamicClient)
799
+ notUseAutodetectedStorageClasses = False
800
+ if defaultStorageClasses.provider is not None:
801
+ self.storageClassProvider = defaultStorageClasses.provider
802
+ print_formatted_text(HTML(f"<MediumSeaGreen>Storage provider auto-detected: {defaultStorageClasses.providerName}</MediumSeaGreen>"))
803
+ print_formatted_text(HTML(f"<LightSlateGrey> - Storage class (ReadWriteMany): {defaultStorageClasses.rwx}</LightSlateGrey>"))
804
+ print_formatted_text(HTML(f"<LightSlateGrey> - Storage class (ReadWriteOnce): {defaultStorageClasses.rwo}</LightSlateGrey>"))
805
+ if self.yesOrNo("Use the auto-detected storage classes"):
806
+ self.printDescription([
807
+ "Storage Mode for Userfiles PVC:",
808
+ " 1. ReadWriteMany",
809
+ " 2. ReadWriteOnce"
810
+ ])
811
+ storageMode = self.promptForListSelect("Select the storage mode for user files PVC:", ["ReadWriteMany", "ReadWriteOnce"], "mas_ws_facilities_storage_userfiles_mode", default=1)
812
+ _ = self.setParam("mas_ws_facilities_storage_userfiles_class", defaultStorageClasses.rwx) if storageMode == "ReadWriteMany" else self.setParam("mas_ws_facilities_storage_userfiles_class", defaultStorageClasses.rwo)
813
+ self.promptForInt("User file PVC size (Gb):", "mas_ws_facilities_storage_userfiles_size", default=50)
814
+ storageMode = self.promptForListSelect("Select the storage mode for log PVC:", ["ReadWriteMany", "ReadWriteOnce"], "mas_ws_facilities_storage_log_mode", default=1)
815
+ _ = self.setParam("mas_ws_facilities_storage_log_class", defaultStorageClasses.rwx) if storageMode == "ReadWriteMany" else self.setParam("mas_ws_facilities_storage_log_class", defaultStorageClasses.rwo)
816
+ self.promptForInt("Log PVC size (Gb):", "mas_ws_facilities_storage_log_size", default=30)
817
+ else:
818
+ notUseAutodetectedStorageClasses = True
819
+ if defaultStorageClasses.provider is None or notUseAutodetectedStorageClasses:
820
+ for storageClass in getStorageClasses(self.dynamicClient):
821
+ print_formatted_text(HTML(f"<LightSlateGrey> - {storageClass.metadata.name}</LightSlateGrey>"))
822
+ self.promptForString("Select storage class for user files PVC:", "mas_ws_facilities_storage_userfiles_class")
823
+ self.promptForString("Select storage class for log PVC:", "mas_ws_facilities_storage_log_class")
824
+ self.printDescription([
825
+ "Storage Mode for Userfiles PVC:",
826
+ " 1. ReadWriteMany",
827
+ " 2. ReadWriteOnce"
828
+ ])
829
+ self.promptForListSelect("Select the storage mode for user files PVC:", ["ReadWriteMany", "ReadWriteOnce"], "mas_ws_facilities_storage_userfiles_mode", default=1)
830
+ self.promptForListSelect("Select the storage mode for log PVC:", ["ReadWriteMany", "ReadWriteOnce"], "mas_ws_facilities_storage_log_mode", default=1)
831
+ self.promptForInt("User file PVC size (Gb):", "mas_ws_facilities_storage_userfiles_size", default=50)
832
+ self.promptForInt("Log PVC size (Gb):", "mas_ws_facilities_storage_log_size", default=30)
833
+
834
+ if self.yesOrNo("Supply configuration for dedicated workflow agents"):
835
+ print_formatted_text(HTML("<LightSlateGrey> Example: '[{\"name\":\"dwfa1\",\"members\":[{\"name\": \"u1\", \"class\": \"user\"}]}, {\"name\":\"dwfa2\",\"members\":[{\"name\": \"u2\", \"class\": \"user\"},{\"name\":\"g1\", \"class\":\"group\"}]}]' </LightSlateGrey>"))
836
+ self.promptForString("Dedicated Workflow Agent JSON:", "mas_ws_facilities_dwfagents", validator=JsonValidator())
837
+
838
+ # If advanced options is selected, we need to create a file to add props not supported by Tekton
839
+ self.selectLocalConfigDir()
840
+ facilitiesConfigsPath = path.join(self.localConfigDir, "facilities-configs.yaml")
841
+ self.generateFacilitiesCfg(destination=facilitiesConfigsPath)
842
+ self.setParam("mas_ws_facilities_config_file", "/workspace/configs/facilities-configs.yaml")
843
+
683
844
  @logMethodCall
684
845
  def chooseInstallFlavour(self) -> None:
685
846
  self.printH1("Choose Install Mode")
@@ -695,8 +856,9 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
695
856
  " - Customize Maximo Manage database settings (schema, tablespace, indexspace)",
696
857
  " - Customize Maximo Manage server bundle configuration (defaults to \"all\" configuration)",
697
858
  " - Enable optional Maximo Manage integration Cognos Analytics and Watson Studio Local",
698
- " - Enable optional Maximo Predict integration with SPSS and Watson OpenScale",
859
+ " - Enable optional Maximo Predict integration with SPSS",
699
860
  " - Enable optional IBM Turbonomic integration",
861
+ " - Enable optional Real Estate and Facilities configurations",
700
862
  " - Customize Db2 node affinity and tolerations, memory, cpu, and storage settings (when using the IBM Db2 Universal Operator)",
701
863
  " - Choose alternative Apache Kafka providers (default to Strimzi)",
702
864
  " - Customize Grafana storage settings"
@@ -727,6 +889,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
727
889
 
728
890
  # Licensing (SLS and DRO)
729
891
  self.configSLS()
892
+ self.configDRO()
730
893
  self.configICRCredentials()
731
894
 
732
895
  # MAS Core
@@ -741,7 +904,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
741
904
  self.optimizerSettings()
742
905
  self.predictSettings()
743
906
  self.assistSettings()
744
- self.aibrokerSettings()
907
+ self.facilitiesSettings()
745
908
 
746
909
  # Dependencies
747
910
  self.configMongoDb()
@@ -771,10 +934,11 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
771
934
  self.installPredict = False
772
935
  self.installInspection = False
773
936
  self.installOptimizer = False
774
- self.installAiBroker = False
937
+ self.installFacilities = False
775
938
  self.deployCP4D = False
776
939
  self.db2SetAffinity = False
777
940
  self.db2SetTolerations = False
941
+ self.slsLicenseFileLocal = None
778
942
 
779
943
  self.approvals = {
780
944
  "approval_core": {"id": "suite-verify"}, # After Core Platform verification has completed
@@ -784,10 +948,13 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
784
948
  "approval_monitor": {"id": "app-cfg-monitor"}, # After Monitor workspace has been configured
785
949
  "approval_optimizer": {"id": "app-cfg-optimizer"}, # After Optimizer workspace has been configured
786
950
  "approval_predict": {"id": "app-cfg-predict"}, # After Predict workspace has been configured
787
- "approval_visualinspection": {"id": "app-cfg-visualinspection"} # After Visual Inspection workspace has been configured
951
+ "approval_visualinspection": {"id": "app-cfg-visualinspection"}, # After Visual Inspection workspace has been configured
952
+ "approval_facilities": {"id": "app-cfg-facilities"}, # After Facilities workspace has been configured 
788
953
  }
789
954
 
790
955
  self.configGrafana()
956
+ self.configSNO()
957
+ self.setDB2DefaultSettings()
791
958
 
792
959
  for key, value in vars(self.args).items():
793
960
  # These fields we just pass straight through to the parameters and fail if they are not set
@@ -865,10 +1032,6 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
865
1032
  if value is not None and value != "":
866
1033
  self.setParam("mas_app_channel_visualinspection", value)
867
1034
  self.installInspection = True
868
- elif key == "aibroker_channel":
869
- if value is not None and value != "":
870
- self.setParam("mas_app_channel_aibroker", value)
871
- self.installAiBroker = True
872
1035
  elif key == "optimizer_channel":
873
1036
  if value is not None and value != "":
874
1037
  self.setParam("mas_app_channel_optimizer", value)
@@ -876,6 +1039,10 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
876
1039
  elif key == "optimizer_plan":
877
1040
  if value is not None and value != "":
878
1041
  self.setParam("mas_app_plan_optimizer", value)
1042
+ elif key == "facilities_channel":
1043
+ if value is not None and value != "":
1044
+ self.setParam("mas_app_channel_facilities", value)
1045
+ self.installFacilities = True
879
1046
 
880
1047
  # Manage advanced settings that need extra processing
881
1048
  elif key == "mas_app_settings_server_bundle_size":
@@ -884,6 +1051,21 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
884
1051
  if value in ["jms", "snojms"]:
885
1052
  self.setParam("mas_app_settings_persistent_volumes_flag", "true")
886
1053
 
1054
+ # MongoDB
1055
+ elif key == "mongodb_namespace":
1056
+ if value is not None and value != "":
1057
+ self.setParam(key, value)
1058
+ self.setParam("sls_mongodb_cfg_file", f"/workspace/configs/mongo-{value}.yml")
1059
+
1060
+ # SLS
1061
+ elif key == "license_file":
1062
+ if value is not None and value != "":
1063
+ self.slsLicenseFileLocal = value
1064
+ self.setParam("sls_action", "install")
1065
+ elif key == "dedicated_sls":
1066
+ if value:
1067
+ self.setParam("sls_namespace", f"mas-{self.args.mas_instance_id}-sls")
1068
+
887
1069
  # These settings are used by the CLI rather than passed to the PipelineRun
888
1070
  elif key == "storage_accessmode":
889
1071
  if value is None:
@@ -893,10 +1075,6 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
893
1075
  if value is None:
894
1076
  self.fatalError(f"{key} must be set")
895
1077
  self.pipelineStorageClass = value
896
- elif key == "license_file":
897
- if value is None:
898
- self.fatalError(f"{key} must be set")
899
- self.slsLicenseFileLocal = value
900
1078
 
901
1079
  elif key.startswith("approval_"):
902
1080
  if key not in self.approvals:
@@ -904,16 +1082,15 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
904
1082
 
905
1083
  if value != "":
906
1084
  valueParts = value.split(":")
907
- if len(valueParts) != 4:
908
- self.fatalError(f"Unsupported format for {key} ({value}). Expected APPROVAL_KEY:MAX_RETRIES:RETRY_DELAY:IGNORE_FAILURE")
1085
+ if len(valueParts) != 3:
1086
+ self.fatalError(f"Unsupported format for {key} ({value}). Expected MAX_RETRIES:RETRY_DELAY:IGNORE_FAILURE")
909
1087
  else:
910
1088
  try:
911
- self.approvals[key]["approvalKey"] = valueParts[0]
912
- self.approvals[key]["maxRetries"] = int(valueParts[1])
913
- self.approvals[key]["retryDelay"] = int(valueParts[2])
914
- self.approvals[key]["ignoreFailure"] = bool(valueParts[3])
1089
+ self.approvals[key]["maxRetries"] = int(valueParts[0])
1090
+ self.approvals[key]["retryDelay"] = int(valueParts[1])
1091
+ self.approvals[key]["ignoreFailure"] = bool(valueParts[2])
915
1092
  except ValueError:
916
- self.fatalError(f"Unsupported format for {key} ({value}). Expected string:int:int:boolean")
1093
+ self.fatalError(f"Unsupported format for {key} ({value}). Expected int:int:boolean")
917
1094
 
918
1095
  # Arguments that we don't need to do anything with
919
1096
  elif key in ["accept_license", "dev_mode", "skip_pre_check", "skip_grafana_install", "no_confirm", "no_wait_for_pvc", "help", "advanced", "simplified"]:
@@ -927,14 +1104,40 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
927
1104
  self.setParam("mas_manual_cert_mgmt", False)
928
1105
  self.manualCertsDir = None
929
1106
 
1107
+ elif key == "enable_ipv6":
1108
+ self.setParam("enable_ipv6", True)
1109
+
930
1110
  # Fail if there's any arguments we don't know how to handle
931
1111
  else:
932
1112
  print(f"Unknown option: {key} {value}")
933
1113
  self.fatalError(f"Unknown option: {key} {value}")
934
1114
 
1115
+ if self.installManage:
1116
+ # If Manage is being installed and --is-full-manage was set to something different than "false", assume it is "true"
1117
+ if self.getParam("is_full_manage") != "false":
1118
+ self.setParam("is_full_manage", "true")
1119
+
1120
+ # Configure Storage and Access mode
1121
+ self.manageStorageAndAccessMode()
1122
+
1123
+ if self.installFacilities:
1124
+ # Verifiy if any of the props that needs to be in a file are given
1125
+ if self.getParam("mas_ws_facilities_storage_log_size") != "" or self.getParam("mas_ws_facilities_storage_userfiles_size") != "" or self.getParam("mas_ws_facilities_db_maxconnpoolsize") or self.getParam("mas_ws_facilities_dwfagents"):
1126
+ self.selectLocalConfigDir()
1127
+ facilitiesConfigsPath = path.join(self.localConfigDir, "facilities-configs.yaml")
1128
+ self.generateFacilitiesCfg(destination=facilitiesConfigsPath)
1129
+ self.setParam("mas_ws_facilities_config_map_name", "facilities-config")
1130
+
935
1131
  # Load the catalog information
936
1132
  self.chosenCatalog = getCatalog(self.getParam("mas_catalog_version"))
937
1133
 
1134
+ # License file is only optional for existing SLS instance
1135
+ if self.slsLicenseFileLocal is None:
1136
+ if findSLSByNamespace(self.getParam("sls_namespace"), dynClient=self.dynamicClient):
1137
+ self.setParam("sls_action", "gencfg")
1138
+ else:
1139
+ self.fatalError("--license-file must be set for new SLS install")
1140
+
938
1141
  # Once we've processed the inputs, we should validate the catalog source & prompt to accept the license terms
939
1142
  if not self.devMode:
940
1143
  self.validateCatalogSource()
@@ -958,6 +1161,10 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
958
1161
  self.devMode = args.dev_mode
959
1162
  self.skipGrafanaInstall = args.skip_grafana_install
960
1163
 
1164
+ # Set image_pull_policy of the CLI in interactive mode
1165
+ if args.image_pull_policy and args.image_pull_policy != "":
1166
+ self.setParam("image_pull_policy", args.image_pull_policy)
1167
+
961
1168
  self.approvals = {}
962
1169
 
963
1170
  # Store all args
@@ -979,6 +1186,10 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
979
1186
  print_formatted_text(HTML("<Red>Error: The Kubernetes dynamic Client is not available. See log file for details</Red>"))
980
1187
  exit(1)
981
1188
 
1189
+ # 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
1190
+ # deprecated MaximoApplicationSuite ImageContentSourcePolicy instead of the new ImageDigestMirrorSet
1191
+ self.isAirgap()
1192
+
982
1193
  # Configure the installOptions for the appropriate architecture
983
1194
  self.catalogOptions = supportedCatalogs[self.architecture]
984
1195
 
@@ -1001,15 +1212,19 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
1001
1212
  if self.deployCP4D:
1002
1213
  self.configCP4D()
1003
1214
 
1004
- # The entitlement file for SLS is mounted as a secret in /workspace/entitlement
1005
- entitlementFileBaseName = path.basename(self.slsLicenseFileLocal)
1006
- self.setParam("sls_entitlement_file", f"/workspace/entitlement/{entitlementFileBaseName}")
1007
-
1008
- # Set up the secrets for additional configs, podtemplates and manual certificates
1215
+ # Set up the secrets for additional configs, podtemplates, sls license file and manual certificates
1009
1216
  self.additionalConfigs()
1010
1217
  self.podTemplates()
1218
+ self.slsLicenseFile()
1011
1219
  self.manualCertificates()
1012
1220
 
1221
+ if not self.noConfirm and not self.waitForPVC:
1222
+ self.printDescription(["If you are using storage classes that utilize 'WaitForFirstConsumer' binding mode choose 'No' at the prompt below"])
1223
+ self.waitForPVC = self.yesOrNo("Wait for PVCs to bind")
1224
+
1225
+ if not self.waitForPVC:
1226
+ self.setParam("no_wait_for_pvc", True)
1227
+
1013
1228
  # Show a summary of the installation configuration
1014
1229
  self.printH1("Non-Interactive Install Command")
1015
1230
  self.printDescription([
@@ -1037,12 +1252,6 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
1037
1252
  self.printH1("Launch Install")
1038
1253
  pipelinesNamespace = f"mas-{self.getParam('mas_instance_id')}-pipelines"
1039
1254
 
1040
- if not self.noConfirm:
1041
- self.printDescription(["If you are using storage classes that utilize 'WaitForFirstConsumer' binding mode choose 'No' at the prompt below"])
1042
- wait = self.yesOrNo("Wait for PVCs to bind")
1043
- else:
1044
- wait = False
1045
-
1046
1255
  with Halo(text='Validating OpenShift Pipelines installation', spinner=self.spinner) as h:
1047
1256
  installOpenShiftPipelines(self.dynamicClient)
1048
1257
  h.stop_and_persist(symbol=self.successIcon, text="OpenShift Pipelines Operator is installed and ready to use")
@@ -1054,12 +1263,13 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
1054
1263
  instanceId=self.getParam("mas_instance_id"),
1055
1264
  storageClass=self.pipelineStorageClass,
1056
1265
  accessMode=self.pipelineStorageAccessMode,
1057
- waitForBind=wait
1266
+ waitForBind=self.waitForPVC,
1267
+ configureRBAC=(self.getParam("service_account_name") == "")
1058
1268
  )
1059
1269
  prepareInstallSecrets(
1060
1270
  dynClient=self.dynamicClient,
1061
1271
  instanceId=self.getParam("mas_instance_id"),
1062
- slsLicenseFile=self.slsLicenseFileLocal,
1272
+ slsLicenseFile=self.slsLicenseFileSecret,
1063
1273
  additionalConfigs=self.additionalConfigsSecret,
1064
1274
  podTemplates=self.podTemplatesSecret,
1065
1275
  certs=self.certsSecret
@@ -1094,11 +1304,11 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
1094
1304
  - present with the chosen state field initialized to ""
1095
1305
  """
1096
1306
  for approval in self.approvals.values():
1097
- if "approvalKey" in approval:
1307
+ if "maxRetries" in approval:
1098
1308
  # Enable this approval workload
1099
- logger.debug(f"Approval workflow for {approval['id']} will be enabled during install ({approval['maxRetries']} / {approval['retryDelay']}s / {approval['approvalKey']} / {approval['ignoreFailure']})")
1100
- self.initializeApprovalConfigMap(namespace, approval['id'], approval['approvalKey'], approval['maxRetries'], approval['retryDelay'], approval['ignoreFailure'])
1309
+ logger.debug(f"Approval workflow for {approval['id']} will be enabled during install ({approval['maxRetries']} / {approval['retryDelay']}s / {approval['ignoreFailure']})")
1310
+ self.initializeApprovalConfigMap(namespace, approval['id'], True, approval['maxRetries'], approval['retryDelay'], approval['ignoreFailure'])
1101
1311
  else:
1102
1312
  # Disable this approval workload
1103
1313
  logger.debug(f"Approval workflow for {approval['id']} will be disabled during install")
1104
- self.initializeApprovalConfigMap(namespace, approval['id'])
1314
+ self.initializeApprovalConfigMap(namespace, approval['id'], False)