mas-cli 10.3.2__py3-none-any.whl → 10.4.1__py3-none-any.whl

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

Potentially problematic release.


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

mas/cli/__init__.py CHANGED
@@ -8,4 +8,4 @@
8
8
  #
9
9
  # *****************************************************************************
10
10
 
11
- __version__ = "10.3.2"
11
+ __version__ = "10.4.1"
mas/cli/cli.py CHANGED
@@ -20,6 +20,7 @@ from sys import exit
20
20
  from openshift import dynamic
21
21
  from kubernetes import config
22
22
  from kubernetes.client import api_client
23
+ from openshift.dynamic.exceptions import NotFoundError
23
24
 
24
25
  from prompt_toolkit import prompt, print_formatted_text, HTML
25
26
 
@@ -198,8 +199,10 @@ class BaseApp(PrintMixin, PromptMixin):
198
199
  # We are already connected to a cluster, but prompt the user if they want to use this connection
199
200
  continueWithExistingCluster = prompt(HTML('<Yellow>Proceed with this cluster?</Yellow> '), validator=YesNoValidator(), validate_while_typing=False)
200
201
  promptForNewServer = continueWithExistingCluster in ["n", "no"]
201
- except Exception:
202
+ except Exception as e:
202
203
  # We are already connected to a cluster, but the connection is not valid so prompt for connection details
204
+ logger.debug("Failed looking up OpenShift Console route to verify connection")
205
+ logger.exception(e, stack_info=True)
203
206
  promptForNewServer = True
204
207
  else:
205
208
  # We are not already connected to any cluster, so prompt for connection details
@@ -214,3 +217,35 @@ class BaseApp(PrintMixin, PromptMixin):
214
217
  if self._dynClient is None:
215
218
  print_formatted_text(HTML("<Red>Unable to connect to cluster. See log file for details</Red>"))
216
219
  exit(1)
220
+
221
+ def initializeApprovalConfigMap(self, namespace: str, id: str, key: str=None, maxRetries: int=100, delay: int=300, ignoreFailure: bool=True) -> None:
222
+ """
223
+ Set key = None if you don't want approval workflow enabled
224
+ """
225
+ cmAPI = self.dynamicClient.resources.get(api_version="v1", kind="ConfigMap")
226
+ configMap = {
227
+ "apiVersion": "v1",
228
+ "kind": "ConfigMap",
229
+ "metadata": {
230
+ "name": f"approval-{id}",
231
+ "namespace": namespace
232
+ },
233
+ "data": {
234
+ "MAX_RETRIES": str(maxRetries),
235
+ "DELAY": str(delay),
236
+ "IGNORE_FAILURE": str(ignoreFailure),
237
+ "CONFIGMAP_KEY": key,
238
+ key: ""
239
+ }
240
+ }
241
+
242
+ # Delete any existing configmap and create a new one
243
+ try:
244
+ logger.debug(f"Deleting any existing approval workflow configmap for {id}")
245
+ cmAPI.delete(name=f"approval-{id}", namespace=namespace)
246
+ except NotFoundError:
247
+ pass
248
+
249
+ if key is not None:
250
+ logger.debug(f"Enabling approval workflow for {id} using {key} with {maxRetries} max retries on a {delay}s delay ({'ignoring failures' if ignoreFailure else 'abort on failure'})")
251
+ cmAPI.create(body=configMap, namespace=namespace)
mas/cli/install/app.py CHANGED
@@ -33,12 +33,20 @@ from mas.cli.validators import (
33
33
  InstanceIDFormatValidator,
34
34
  WorkspaceIDFormatValidator,
35
35
  WorkspaceNameFormatValidator,
36
+ TimeoutFormatValidator,
36
37
  StorageClassValidator,
37
38
  OptimizerInstallPlanValidator
38
39
  )
39
40
 
40
41
  from mas.devops.ocp import createNamespace, getStorageClass, getStorageClasses
41
- from mas.devops.tekton import installOpenShiftPipelines, updateTektonDefinitions, preparePipelinesNamespace, prepareInstallSecrets, testCLI, launchInstallPipeline
42
+ from mas.devops.tekton import (
43
+ installOpenShiftPipelines,
44
+ updateTektonDefinitions,
45
+ preparePipelinesNamespace,
46
+ prepareInstallSecrets,
47
+ testCLI,
48
+ launchInstallPipeline
49
+ )
42
50
 
43
51
  logger = logging.getLogger(__name__)
44
52
 
@@ -51,6 +59,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
51
59
  catalogDisplayName = catalog.spec.displayName
52
60
 
53
61
  m = re.match(r".+(?P<catalogId>v[89]-(?P<catalogVersion>[0-9]+)-amd64)", catalogDisplayName)
62
+ print(f"m: {m}")
54
63
  if m:
55
64
  # catalogId = v8-yymmdd-amd64
56
65
  # catalogVersion = yymmdd
@@ -187,6 +196,23 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
187
196
 
188
197
  self.deployCP4D = True
189
198
 
199
+ def configSSOProperties(self):
200
+ self.printH1("Single Sign-On (SSO)")
201
+ self.printDescription([
202
+ "Many aspects of Maximo Application Suite's Single Sign-On (SSO) can be customized:",
203
+ " - Idle session automatic logout timer",
204
+ " - Session, access token, and refresh token timeouts",
205
+ " - Default identity provider (IDP), and seamless login"
206
+ ])
207
+ sso_response = self.yesOrNo("Configure SSO properties")
208
+ if sso_response:
209
+ self.promptForInt("Enter the idle timeout (in seconds)", "idle_timeout", default=1800)
210
+ self.promptForString("Enter the IDP session timeout (e.g., '12h' for 12 hours)", "idp_session_timeout", validator=TimeoutFormatValidator(), default="12h")
211
+ self.promptForString("Enter the access token timeout (e.g., '30m' for 30 minutes)", "access_token_timeout", validator=TimeoutFormatValidator(), default="30m")
212
+ self.promptForString("Enter the refresh token timeout (e.g., '12h' for 12 hours)", "refresh_token_timeout", validator=TimeoutFormatValidator(), default="12h")
213
+ self.promptForString("Enter the default Identity Provider (IDP)", "default_idp", default="local")
214
+ self.yesOrNo("Enable seamless login?", param="seamless_login")
215
+
190
216
  def configMAS(self):
191
217
  self.printH1("Configure MAS Instance")
192
218
  self.printDescription([
@@ -215,6 +241,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
215
241
  self.configOperationMode()
216
242
  self.configCATrust()
217
243
  self.configDNSAndCerts()
244
+ self.configSSOProperties()
218
245
 
219
246
  def configCATrust(self) -> None:
220
247
  self.printH1("Certificate Authority Trust")
@@ -568,22 +595,38 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
568
595
  self.db2SetAffinity = False
569
596
  self.db2SetTolerations = False
570
597
 
598
+ self.approvals = {
599
+ "approval_core": {"id": "suite-verify"}, # After Core Platform verification has completed
600
+ "approval_assist": {"id": "app-cfg-assist"}, # After Assist workspace has been configured
601
+ "approval_iot": {"id": "app-cfg-iot"}, # After IoT workspace has been configured
602
+ "approval_manage": {"id": "app-cfg-manage"}, # After Manage workspace has been configured
603
+ "approval_monitor": {"id": "app-cfg-monitor"}, # After Monitor workspace has been configured
604
+ "approval_optimizer": {"id": "app-cfg-optimizer"}, # After Optimizer workspace has been configured
605
+ "approval_predict": {"id": "app-cfg-predict"}, # After Predict workspace has been configured
606
+ "approval_visualinspection": {"id": "app-cfg-visualinspection"} # After Visual Inspection workspace has been configured
607
+ }
608
+
571
609
  self.configGrafana()
572
610
 
573
611
  requiredParams = [
612
+ # MAS
574
613
  "mas_catalog_version",
575
614
  "mas_channel",
576
615
  "mas_instance_id",
577
616
  "mas_workspace_id",
578
617
  "mas_workspace_name",
618
+ # Storage classes
579
619
  "storage_class_rwo",
580
620
  "storage_class_rwx",
621
+ # Entitlement
581
622
  "ibm_entitlement_key",
623
+ # DRO
582
624
  "uds_contact_email",
583
625
  "uds_contact_firstname",
584
626
  "uds_contact_lastname"
585
627
  ]
586
628
  optionalParams = [
629
+ # MAS
587
630
  "mas_superuser_username",
588
631
  "mas_superuser_password",
589
632
  "mas_trust_default_cas",
@@ -609,9 +652,11 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
609
652
  "mas_app_settings_secondary_langs",
610
653
  "mas_app_settings_server_timezone",
611
654
  "ocp_ingress_tls_secret_name",
655
+ # DRO
612
656
  "dro_namespace",
657
+ # MongoDb
613
658
  "mongodb_namespace",
614
- "cpd_product_version",
659
+ # Db2
615
660
  "db2_action_system",
616
661
  "db2_action_manage",
617
662
  "db2_type",
@@ -632,9 +677,12 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
632
677
  "db2_logs_storage_size",
633
678
  "db2_meta_storage_size",
634
679
  "db2_temp_storage_size",
680
+ # CP4D
681
+ "cpd_product_version",
635
682
  "cpd_install_cognos",
636
683
  "cpd_install_openscale",
637
684
  "cpd_install_spss",
685
+ # Kafka
638
686
  "kafka_namespace",
639
687
  "kafka_version",
640
688
  "aws_msk_instance_type",
@@ -648,21 +696,27 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
648
696
  "eventstreams_resource_group",
649
697
  "eventstreams_instance_name",
650
698
  "eventstreams_instance_location",
699
+ # ECK
651
700
  "eck_action",
652
701
  "eck_enable_logstash",
653
702
  "eck_remote_es_hosts",
654
703
  "eck_remote_es_username",
655
704
  "eck_remote_es_password",
705
+ # Turbonomic
656
706
  "turbonomic_target_name",
657
707
  "turbonomic_server_url",
658
708
  "turbonomic_server_version",
659
709
  "turbonomic_username",
660
710
  "turbonomic_password",
711
+ # Cloud Providers
661
712
  "ibmcloud_apikey",
662
713
  "aws_region",
663
714
  "aws_access_key_id",
664
715
  "secret_access_key",
665
716
  "aws_vpc_id",
717
+ # Dev Mode
718
+ "artifactory_username",
719
+ "artifactory_token",
666
720
  # TODO: The way arcgis has been implemented needs to be fixed
667
721
  "install_arcgis",
668
722
  "mas_arcgis_channel"
@@ -765,6 +819,23 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
765
819
  self.fatalError(f"{key} must be set")
766
820
  self.slsLicenseFileLocal = value
767
821
 
822
+ elif key.startswith("approval_"):
823
+ if key not in self.approvals:
824
+ raise KeyError(f"{key} is not a supported approval workflow ID: {self.approvals.keys()}")
825
+
826
+ if value != "":
827
+ valueParts = value.split(":")
828
+ if len(valueParts) != 4:
829
+ self.fatalError(f"Unsupported format for {key} ({value}). Expected APPROVAL_KEY:MAX_RETRIES:RETRY_DELAY:IGNORE_FAILURE")
830
+ else:
831
+ try:
832
+ self.approvals[key]["approvalKey"] = valueParts[0]
833
+ self.approvals[key]["maxRetries"] = int(valueParts[1])
834
+ self.approvals[key]["retryDelay"] = int(valueParts[2])
835
+ self.approvals[key]["ignoreFailure"] = bool(valueParts[3])
836
+ except:
837
+ self.fatalError(f"Unsupported format for {key} ({value}). Expected string:int:int:boolean")
838
+
768
839
  # Arguments that we don't need to do anything with
769
840
  elif key in ["accept_license", "dev_mode", "skip_pre_check", "no_confirm", "no_wait_for_pvc", "help"]:
770
841
  pass
@@ -958,6 +1029,9 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
958
1029
  podTemplates=self.podTemplatesSecret,
959
1030
  certs=self.certsSecret
960
1031
  )
1032
+
1033
+ self.setupApprovals(pipelinesNamespace)
1034
+
961
1035
  h.stop_and_persist(symbol=self.successIcon, text=f"Namespace is ready ({pipelinesNamespace})")
962
1036
 
963
1037
  with Halo(text=f'Testing availability of MAS CLI image in cluster', spinner=self.spinner) as h:
@@ -976,3 +1050,19 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
976
1050
  else:
977
1051
  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")
978
1052
  print()
1053
+
1054
+ def setupApprovals(self, namespace: str) -> None:
1055
+ """
1056
+ Ensure the supported approval configmaps are in the expected state for the start of the run:
1057
+ - not present (if approval is not required)
1058
+ - present with the chosen state field initialized to ""
1059
+ """
1060
+ for approval in self.approvals.values():
1061
+ if "approvalKey" in approval:
1062
+ # Enable this approval workload
1063
+ logger.debug(f"Approval workflow for {approval['id']} will be enabled during install ({approval['maxRetries']} / {approval['retryDelay']}s / {approval['approvalKey']} / {approval['ignoreFailure']})")
1064
+ self.initializeApprovalConfigMap(namespace, approval['id'], approval['approvalKey'], approval['maxRetries'], approval['retryDelay'], approval['ignoreFailure'])
1065
+ else:
1066
+ # Disable this approval workload
1067
+ logger.debug(f"Approval workflow for {approval['id']} will be disabled during install")
1068
+ self.initializeApprovalConfigMap(namespace, approval['id'])
@@ -146,7 +146,7 @@ masAdvancedArgGroup.add_argument(
146
146
  masAdvancedArgGroup.add_argument(
147
147
  "--manual-certificates",
148
148
  required=False,
149
- help="Please enter the path containing the manual certificates for core and the apps to be installed"
149
+ help="Path to directory containing the certificates to be applied"
150
150
  )
151
151
 
152
152
  # Storage
@@ -756,12 +756,73 @@ cloudArgGroup.add_argument(
756
756
  help="Set target Virtual Private Cloud ID for the MSK instance"
757
757
  )
758
758
 
759
+ # Development Mode
760
+ # -----------------------------------------------------------------------------
761
+ devArgGroup = installArgParser.add_argument_group("Development Mode")
762
+ devArgGroup.add_argument(
763
+ "--artifactory-username",
764
+ required=False,
765
+ help="Username for access to development builds on Artifactory"
766
+ )
767
+ devArgGroup.add_argument(
768
+ "--artifactory-token",
769
+ required=False,
770
+ help="API Token for access to development builds on Artifactory"
771
+ )
772
+
773
+ # Approvals
774
+ # -----------------------------------------------------------------------------
775
+ approvalsGroup = installArgParser.add_argument_group("Integrated Approval Workflow (APPROVAL_KEY:MAX_RETRIES:RETRY_DELAY:IGNORE_FAILURE)")
776
+ approvalsGroup.add_argument(
777
+ "--approval-core",
778
+ default="",
779
+ help="Require approval after the Core Platform has been configured"
780
+ )
781
+ approvalsGroup.add_argument(
782
+ "--approval-assist",
783
+ default="",
784
+ help="Require approval after the Maximo Assist workspace has been configured"
785
+ )
786
+ approvalsGroup.add_argument(
787
+ "--approval-iot",
788
+ default="",
789
+ help="Require approval after the Maximo IoT workspace has been configured"
790
+ )
791
+ approvalsGroup.add_argument(
792
+ "--approval-manage",
793
+ default="",
794
+ help="Require approval after the Maximo Manage workspace has been configured"
795
+ )
796
+ approvalsGroup.add_argument(
797
+ "--approval-monitor",
798
+ default="",
799
+ help="Require approval after the Maximo Monitor workspace has been configured"
800
+ )
801
+ approvalsGroup.add_argument(
802
+ "--approval-optimizer",
803
+ default="",
804
+ help="Require approval after the Maximo Optimizer workspace has been configured"
805
+ )
806
+ approvalsGroup.add_argument(
807
+ "--approval-predict",
808
+ default="",
809
+ help="Require approval after the Maximo Predict workspace has been configured"
810
+ )
811
+ approvalsGroup.add_argument(
812
+ "--approval-visualinspection",
813
+ default="",
814
+ help="Require approval after the Maximo Visual Inspection workspace has been configured"
815
+ )
816
+
817
+
818
+ # More Options
819
+ # -----------------------------------------------------------------------------
759
820
  otherArgGroup = installArgParser.add_argument_group("More")
760
821
  otherArgGroup.add_argument(
761
822
  "--accept-license",
762
823
  action="store_true",
763
824
  default=False,
764
- help=""
825
+ help="Accept all license terms without prompting"
765
826
  )
766
827
  otherArgGroup.add_argument(
767
828
  "--dev-mode",