mas-cli 10.7.3__py3-none-any.whl → 10.9.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/__init__.py CHANGED
@@ -8,4 +8,4 @@
8
8
  #
9
9
  # *****************************************************************************
10
10
 
11
- __version__ = "10.7.3"
11
+ __version__ = "10.9.0" # Python module compatible semver
mas/cli/cli.py CHANGED
@@ -15,6 +15,9 @@ from argparse import RawTextHelpFormatter
15
15
  from shutil import which
16
16
  from os import path, environ
17
17
  from sys import exit
18
+ from subprocess import PIPE, Popen, TimeoutExpired
19
+ import threading
20
+ import json
18
21
 
19
22
  # Use of the openshift client rather than the kubernetes client allows us access to "apply"
20
23
  from kubernetes import config
@@ -24,9 +27,9 @@ from openshift.dynamic.exceptions import NotFoundError
24
27
 
25
28
  from prompt_toolkit import prompt, print_formatted_text, HTML
26
29
 
30
+ from mas.devops.mas import isAirgapInstall
27
31
  from mas.devops.ocp import connect, isSNO
28
32
 
29
- from . import __version__ as packageVersion
30
33
  from .validators import YesNoValidator
31
34
  from .displayMixins import PrintMixin, PromptMixin
32
35
 
@@ -36,6 +39,7 @@ logger = logging.getLogger(__name__)
36
39
  # Disable warnings when users are connecting to OCP clusters with self-signed certificates
37
40
  urllib3.disable_warnings()
38
41
 
42
+
39
43
  def getHelpFormatter(formatter=RawTextHelpFormatter, w=160, h=50):
40
44
  """
41
45
  Return a wider HelpFormatter, if possible.
@@ -51,6 +55,41 @@ def getHelpFormatter(formatter=RawTextHelpFormatter, w=160, h=50):
51
55
  return formatter
52
56
 
53
57
 
58
+ class RunCmdResult(object):
59
+ def __init__(self, returnCode, output, error):
60
+ self.rc = returnCode
61
+ self.out = output
62
+ self.err = error
63
+
64
+ def successful(self):
65
+ return self.rc == 0
66
+
67
+ def failed(self):
68
+ return self.rc != 0
69
+
70
+
71
+ def runCmd(cmdArray, timeout=630):
72
+ """
73
+ Run a command on the local host. This drives all the helm operations,
74
+ as there is no python Helm client available.
75
+ # Parameters
76
+ cmdArray (list<string>): Command to execute
77
+ timeout (int): How long to allow for the command to complete
78
+ # Returns
79
+ [int, string, string]: `returnCode`, `stdOut`, `stdErr`
80
+ """
81
+
82
+ lock = threading.Lock()
83
+
84
+ with lock:
85
+ p = Popen(cmdArray, stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1)
86
+ try:
87
+ output, error = p.communicate(timeout=timeout)
88
+ return RunCmdResult(p.returncode, output, error)
89
+ except TimeoutExpired as e:
90
+ return RunCmdResult(127, 'TimeoutExpired', str(e))
91
+
92
+
54
93
  class BaseApp(PrintMixin, PromptMixin):
55
94
  def __init__(self):
56
95
  # Set up a log formatter
@@ -68,13 +107,18 @@ class BaseApp(PrintMixin, PromptMixin):
68
107
  rootLogger.addHandler(ch)
69
108
  rootLogger.setLevel(logging.DEBUG)
70
109
 
71
- self.version = packageVersion
110
+ # Supports extended semver, unlike mas.cli.__version__
111
+ self.version = "10.9.0"
72
112
  self.h1count = 0
73
113
  self.h2count = 0
74
114
 
75
115
  self.localConfigDir = None
76
116
  self.templatesDir = path.join(path.abspath(path.dirname(__file__)), "templates")
77
- self.tektonDefsPath = path.join(self.templatesDir, "ibm-mas-tekton.yaml")
117
+ self.tektonDefsWithoutDigestPath = path.join(self.templatesDir, "ibm-mas-tekton.yaml")
118
+ self.tektonDefsWithDigestPath = path.join(self.templatesDir, "ibm-mas-tekton-with-digest.yaml")
119
+
120
+ # Default to using the tekton definitions without image digests
121
+ self.tektonDefsPath = self.tektonDefsWithoutDigestPath
78
122
 
79
123
  # Initialize the dictionary that will hold the parameters we pass to a PipelineRun
80
124
  self.params = dict()
@@ -131,6 +175,48 @@ class BaseApp(PrintMixin, PromptMixin):
131
175
  if which("kubectl") is None:
132
176
  self.fatalError("Could not find kubectl on the path, see <DarkGoldenRod><u>https://kubernetes.io/docs/tasks/tools/#kubectl</u></DarkGoldenRod> for installation instructions")
133
177
 
178
+ def createTektonFileWithDigest(self) -> None:
179
+ if path.exists(self.tektonDefsWithDigestPath):
180
+ logger.debug(f"We have already generated {self.tektonDefsWithDigestPath}")
181
+ elif isAirgapInstall(self.dynamicClient):
182
+ # We need to modify the tekton definitions to
183
+ imageWithoutDigest = f"quay.io/ibmmas/cli:{self.version}"
184
+ self.printH1("Disconnected OpenShift Preparation")
185
+ self.printDescription([
186
+ f"Unless the {imageWithoutDigest} image is accessible from your cluster the MAS CLI container image must be present in your mirror registry"
187
+ ])
188
+ cmdArray = ["skopeo", "inspect", f"docker://{imageWithoutDigest}"]
189
+ logger.info(f"Skopeo inspect command: {' '.join(cmdArray)}")
190
+ skopeoResult = runCmd(cmdArray)
191
+ if skopeoResult.successful():
192
+ skopeoData = json.loads(skopeoResult.out)
193
+ logger.info(f"Skopeo Data for {imageWithoutDigest}: {skopeoData}")
194
+ if "Digest" not in skopeoData:
195
+ self.fatalError("Recieved bad data inspecting CLI manifest to determine digest")
196
+ cliImageDigest = skopeoData["Digest"]
197
+ else:
198
+ warning = f"Unable to retrieve image digest for {imageWithoutDigest} ({skopeoResult.rc})"
199
+ self.printWarning(warning)
200
+ logger.warning(warning)
201
+ logger.warning(skopeoResult.err)
202
+ if self.noConfirm:
203
+ self.fatalError("Unable to automatically determine CLI image digest and --no-confirm flag has been set")
204
+ else:
205
+ cliImageDigest = self.promptForString(f"Enter {imageWithoutDigest} image digest")
206
+
207
+ # Overwrite the tekton definitions with one that uses the looked up image digest
208
+ imageWithDigest = f"quay.io/ibmmas/cli@{cliImageDigest}"
209
+ self.printHighlight(f"\nConverting Tekton definitions to use {imageWithDigest}")
210
+ with open(self.tektonDefsPath, 'r') as file:
211
+ tektonDefsWithoutDigest = file.read()
212
+
213
+ tektonDefsWithDigest = tektonDefsWithoutDigest.replace(imageWithoutDigest, imageWithDigest)
214
+
215
+ with open(self.tektonDefsWithDigestPath, 'w') as file:
216
+ file.write(tektonDefsWithDigest)
217
+
218
+ self.tektonDefsPath = self.tektonDefsWithDigestPath
219
+
134
220
  def getCompatibleVersions(self, coreChannel: str, appId: str) -> list:
135
221
  if coreChannel in self.compatibilityMatrix:
136
222
  return self.compatibilityMatrix[coreChannel][appId]
mas/cli/install/app.py CHANGED
@@ -25,6 +25,7 @@ from halo import Halo
25
25
 
26
26
  from ..cli import BaseApp
27
27
  from ..gencfg import ConfigGeneratorMixin
28
+ from .argBuilder import installArgBuilderMixin
28
29
  from .argParser import installArgParser
29
30
  from .settings import InstallSettingsMixin
30
31
  from .summarizer import InstallSummarizerMixin
@@ -51,7 +52,7 @@ from mas.devops.tekton import (
51
52
  logger = logging.getLogger(__name__)
52
53
 
53
54
 
54
- class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGeneratorMixin):
55
+ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGeneratorMixin, installArgBuilderMixin):
55
56
  def validateCatalogSource(self):
56
57
  catalogsAPI = self.dynamicClient.resources.get(api_version="operators.coreos.com/v1alpha1", kind="CatalogSource")
57
58
  try:
@@ -185,17 +186,19 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
185
186
  self.promptForString("Install namespace", "mongodb_namespace", default="mongoce")
186
187
 
187
188
  def configCP4D(self):
188
- # TODO: It's probably time to remove v8-amd64 support from the CLI entirely now
189
- if self.getParam("mas_catalog_version") in ["v8-amd64", "v9-240625-amd64", "v9-240730-amd64"]:
189
+ if self.getParam("mas_catalog_version") in ["v9-240625-amd64", "v9-240730-amd64", "v9-240827-amd64"]:
190
+ logger.debug(f"Using automatic CP4D product version: {self.getParam('cpd_product_version')}")
190
191
  self.setParam("cpd_product_version", "4.8.0")
191
- elif self.getParam("mas_catalog_version") in ["v8-240528-amd64"]:
192
- self.setParam("cpd_product_version", "4.6.6")
193
- else:
192
+ elif self.getParam("cpd_product_version") == "":
193
+ if self.noConfirm:
194
+ self.fatalError("Cloud Pak for Data version must be set manually, but --no-confirm flag has been set")
194
195
  self.printDescription([
195
196
  f"Unknown catalog {self.getParam('mas_catalog_version')}, please manually select the version of Cloud Pak for Data to use"
196
197
  ])
197
198
  self.promptForString("Cloud Pak for Data product version", "cpd_product_version", default="4.8.0")
198
-
199
+ logger.debug(f"Using user-provided (prompt) CP4D product version: {self.getParam('cpd_product_version')}")
200
+ else:
201
+ logger.debug(f"Using user-provided (flags) CP4D product version: {self.getParam('cpd_product_version')}")
199
202
  self.deployCP4D = True
200
203
 
201
204
  def configSSOProperties(self):
@@ -221,6 +224,14 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
221
224
  self.yesOrNo("Disable LDAP cookie", "disable_ldap_cookie")
222
225
  self.yesOrNo("Allow custom cache key", "allow_custom_cache_key")
223
226
 
227
+ def configGuidedTour(self):
228
+ self.printH1("Enable Guided Tour")
229
+ self.printDescription([
230
+ "By default, Maximo Application Suite is configured with guided tour, you can disable this if it not required"
231
+ ])
232
+ if not self.yesOrNo("Enable Guided Tour"):
233
+ self.setParam("mas_enable_walkme","false")
234
+
224
235
  def configMAS(self):
225
236
  self.printH1("Configure MAS Instance")
226
237
  self.printDescription([
@@ -250,6 +261,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
250
261
  self.configCATrust()
251
262
  self.configDNSAndCerts()
252
263
  self.configSSOProperties()
264
+ self.configGuidedTour()
253
265
 
254
266
  def configCATrust(self) -> None:
255
267
  self.printH1("Certificate Authority Trust")
@@ -469,11 +481,11 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
469
481
  # 3. Azure
470
482
  elif getStorageClass(self.dynamicClient, "managed-premium") is not None:
471
483
  print_formatted_text(HTML("<MediumSeaGreen>Storage provider auto-detected: Azure Managed</MediumSeaGreen>"))
472
- print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteOnce): azurefiles-premium</LightSlateGrey>"))
473
- print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteMany): managed-premium</LightSlateGrey>"))
484
+ print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteOnce): managed-premium</LightSlateGrey>"))
485
+ print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteMany): azurefiles-premium</LightSlateGrey>"))
474
486
  self.storageClassProvider = "azure"
475
- self.params["storage_class_rwo"] = "azurefiles-premium"
476
- self.params["storage_class_rwx"] = "managed-premium"
487
+ self.params["storage_class_rwo"] = "managed-premium"
488
+ self.params["storage_class_rwx"] = "azurefiles-premium"
477
489
  # 4. AWS
478
490
  elif getStorageClass(self.dynamicClient, "gp2") is not None:
479
491
  print_formatted_text(HTML("<MediumSeaGreen>Storage provider auto-detected: AWS gp2</MediumSeaGreen>"))
@@ -635,6 +647,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
635
647
  ]
636
648
  optionalParams = [
637
649
  # MAS
650
+ "mas_catalog_digest",
638
651
  "mas_superuser_username",
639
652
  "mas_superuser_password",
640
653
  "mas_trust_default_cas",
@@ -704,6 +717,9 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
704
717
  "eventstreams_resource_group",
705
718
  "eventstreams_instance_name",
706
719
  "eventstreams_instance_location",
720
+ # COS
721
+ "cos_type",
722
+ "cos_resourcegroup",
707
723
  # ECK
708
724
  "eck_action",
709
725
  "eck_enable_logstash",
@@ -727,7 +743,9 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
727
743
  "artifactory_token",
728
744
  # TODO: The way arcgis has been implemented needs to be fixed
729
745
  "install_arcgis",
730
- "mas_arcgis_channel"
746
+ "mas_arcgis_channel",
747
+ # Guided Tour
748
+ "mas_enable_walkme"
731
749
  ]
732
750
 
733
751
  for key, value in vars(self.args).items():
@@ -890,10 +908,49 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
890
908
  # These flags work for setting params in both interactive and non-interactive modes
891
909
  if args.skip_pre_check:
892
910
  self.setParam("skip_pre_check", "true")
893
-
911
+
894
912
  self.installOptions = [
895
913
  {
896
914
  "#": 1,
915
+ "catalog": "v9-240827-amd64",
916
+ "release": "9.0.x",
917
+ "core": "9.0.2",
918
+ "assist": "9.0.2",
919
+ "iot": "9.0.2",
920
+ "manage": "9.0.2",
921
+ "monitor": "9.0.2",
922
+ "optimizer": "9.0.2",
923
+ "predict": "9.0.1",
924
+ "inspection": "9.0.2"
925
+ },
926
+ {
927
+ "#": 2,
928
+ "catalog": "v9-240827-amd64",
929
+ "release": "8.11.x",
930
+ "core": "8.11.14",
931
+ "assist": "8.8.6",
932
+ "iot": "8.8.12",
933
+ "manage": "8.7.11",
934
+ "monitor": "8.11.10",
935
+ "optimizer": "8.5.8",
936
+ "predict": "8.9.3",
937
+ "inspection": "8.9.5"
938
+ },
939
+ {
940
+ "#": 3,
941
+ "catalog": "v9-240827-amd64",
942
+ "release": "8.10.x",
943
+ "core": "8.10.17",
944
+ "assist": "8.7.7",
945
+ "iot": "8.7.16",
946
+ "manage": "8.6.17",
947
+ "monitor": "8.10.13",
948
+ "optimizer": "8.4.9",
949
+ "predict": "8.8.3",
950
+ "inspection": "8.8.4"
951
+ },
952
+ {
953
+ "#": 4,
897
954
  "catalog": "v9-240730-amd64",
898
955
  "release": "9.0.x",
899
956
  "core": "9.0.1",
@@ -906,7 +963,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
906
963
  "inspection": "9.0.0"
907
964
  },
908
965
  {
909
- "#": 2,
966
+ "#": 5,
910
967
  "catalog": "v9-240730-amd64",
911
968
  "release": "8.11.x",
912
969
  "core": "8.11.13",
@@ -919,7 +976,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
919
976
  "inspection": "8.9.4"
920
977
  },
921
978
  {
922
- "#": 3,
979
+ "#": 6,
923
980
  "catalog": "v9-240730-amd64",
924
981
  "release": "8.10.x",
925
982
  "core": "8.10.16",
@@ -932,7 +989,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
932
989
  "inspection": "8.8.4"
933
990
  },
934
991
  {
935
- "#": 4,
992
+ "#": 7,
936
993
  "catalog": "v9-240625-amd64",
937
994
  "release": "9.0.x",
938
995
  "core": "9.0.0",
@@ -945,7 +1002,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
945
1002
  "inspection": "9.0.0"
946
1003
  },
947
1004
  {
948
- "#": 5,
1005
+ "#": 8,
949
1006
  "catalog": "v9-240625-amd64",
950
1007
  "release": "8.11.x",
951
1008
  "core": "8.11.12",
@@ -958,7 +1015,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
958
1015
  "inspection": "8.9.3"
959
1016
  },
960
1017
  {
961
- "#": 6,
1018
+ "#": 9,
962
1019
  "catalog": "v9-240625-amd64",
963
1020
  "release": "8.10.x",
964
1021
  "core": "8.10.15",
@@ -969,32 +1026,6 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
969
1026
  "optimizer": "8.4.7",
970
1027
  "predict": "N/A",
971
1028
  "inspection": "8.8.4"
972
- },
973
- {
974
- "#": 7,
975
- "catalog": "v8-240528-amd64",
976
- "release": "8.11.x",
977
- "core": "8.11.11",
978
- "assist": "N/A",
979
- "iot": "8.8.9",
980
- "manage": "8.7.8",
981
- "monitor": "8.11.7",
982
- "optimizer": "8.5.5",
983
- "predict": "8.9.2",
984
- "inspection": "8.9.3"
985
- },
986
- {
987
- "#": 8,
988
- "catalog": "v8-240528-amd64",
989
- "release": "8.10.x",
990
- "core": "8.10.14",
991
- "assist": "N/A",
992
- "iot": "8.7.13",
993
- "manage": "8.6.14",
994
- "monitor": "8.10.10",
995
- "optimizer": "8.4.6",
996
- "predict": "N/A",
997
- "inspection": "8.8.4"
998
1029
  }
999
1030
  ]
1000
1031
 
@@ -1038,6 +1069,13 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
1038
1069
  self.manualCertificates()
1039
1070
 
1040
1071
  # Show a summary of the installation configuration
1072
+ self.printH1("Non-Interactive Install Command")
1073
+ self.printDescription([
1074
+ "Save and re-use the following script to re-run this install without needing to answer the interactive prompts again",
1075
+ "",
1076
+ self.buildCommand()
1077
+ ])
1078
+
1041
1079
  self.displayInstallSummary()
1042
1080
 
1043
1081
  if not self.noConfirm:
@@ -1049,6 +1087,8 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
1049
1087
 
1050
1088
  # Prepare the namespace and launch the installation pipeline
1051
1089
  if self.noConfirm or continueWithInstall:
1090
+ self.createTektonFileWithDigest()
1091
+
1052
1092
  self.printH1("Launch Install")
1053
1093
  pipelinesNamespace = f"mas-{self.getParam('mas_instance_id')}-pipelines"
1054
1094