mas-cli 10.8.0__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.8.0"
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):
@@ -478,11 +481,11 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
478
481
  # 3. Azure
479
482
  elif getStorageClass(self.dynamicClient, "managed-premium") is not None:
480
483
  print_formatted_text(HTML("<MediumSeaGreen>Storage provider auto-detected: Azure Managed</MediumSeaGreen>"))
481
- print_formatted_text(HTML("<LightSlateGrey> - Storage class (ReadWriteOnce): azurefiles-premium</LightSlateGrey>"))
482
- 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>"))
483
486
  self.storageClassProvider = "azure"
484
- self.params["storage_class_rwo"] = "azurefiles-premium"
485
- self.params["storage_class_rwx"] = "managed-premium"
487
+ self.params["storage_class_rwo"] = "managed-premium"
488
+ self.params["storage_class_rwx"] = "azurefiles-premium"
486
489
  # 4. AWS
487
490
  elif getStorageClass(self.dynamicClient, "gp2") is not None:
488
491
  print_formatted_text(HTML("<MediumSeaGreen>Storage provider auto-detected: AWS gp2</MediumSeaGreen>"))
@@ -644,6 +647,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
644
647
  ]
645
648
  optionalParams = [
646
649
  # MAS
650
+ "mas_catalog_digest",
647
651
  "mas_superuser_username",
648
652
  "mas_superuser_password",
649
653
  "mas_trust_default_cas",
@@ -713,6 +717,9 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
713
717
  "eventstreams_resource_group",
714
718
  "eventstreams_instance_name",
715
719
  "eventstreams_instance_location",
720
+ # COS
721
+ "cos_type",
722
+ "cos_resourcegroup",
716
723
  # ECK
717
724
  "eck_action",
718
725
  "eck_enable_logstash",
@@ -901,10 +908,49 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
901
908
  # These flags work for setting params in both interactive and non-interactive modes
902
909
  if args.skip_pre_check:
903
910
  self.setParam("skip_pre_check", "true")
904
-
911
+
905
912
  self.installOptions = [
906
913
  {
907
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,
908
954
  "catalog": "v9-240730-amd64",
909
955
  "release": "9.0.x",
910
956
  "core": "9.0.1",
@@ -917,7 +963,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
917
963
  "inspection": "9.0.0"
918
964
  },
919
965
  {
920
- "#": 2,
966
+ "#": 5,
921
967
  "catalog": "v9-240730-amd64",
922
968
  "release": "8.11.x",
923
969
  "core": "8.11.13",
@@ -930,7 +976,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
930
976
  "inspection": "8.9.4"
931
977
  },
932
978
  {
933
- "#": 3,
979
+ "#": 6,
934
980
  "catalog": "v9-240730-amd64",
935
981
  "release": "8.10.x",
936
982
  "core": "8.10.16",
@@ -943,7 +989,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
943
989
  "inspection": "8.8.4"
944
990
  },
945
991
  {
946
- "#": 4,
992
+ "#": 7,
947
993
  "catalog": "v9-240625-amd64",
948
994
  "release": "9.0.x",
949
995
  "core": "9.0.0",
@@ -956,7 +1002,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
956
1002
  "inspection": "9.0.0"
957
1003
  },
958
1004
  {
959
- "#": 5,
1005
+ "#": 8,
960
1006
  "catalog": "v9-240625-amd64",
961
1007
  "release": "8.11.x",
962
1008
  "core": "8.11.12",
@@ -969,7 +1015,7 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
969
1015
  "inspection": "8.9.3"
970
1016
  },
971
1017
  {
972
- "#": 6,
1018
+ "#": 9,
973
1019
  "catalog": "v9-240625-amd64",
974
1020
  "release": "8.10.x",
975
1021
  "core": "8.10.15",
@@ -980,32 +1026,6 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
980
1026
  "optimizer": "8.4.7",
981
1027
  "predict": "N/A",
982
1028
  "inspection": "8.8.4"
983
- },
984
- {
985
- "#": 7,
986
- "catalog": "v8-240528-amd64",
987
- "release": "8.11.x",
988
- "core": "8.11.11",
989
- "assist": "N/A",
990
- "iot": "8.8.9",
991
- "manage": "8.7.8",
992
- "monitor": "8.11.7",
993
- "optimizer": "8.5.5",
994
- "predict": "8.9.2",
995
- "inspection": "8.9.3"
996
- },
997
- {
998
- "#": 8,
999
- "catalog": "v8-240528-amd64",
1000
- "release": "8.10.x",
1001
- "core": "8.10.14",
1002
- "assist": "N/A",
1003
- "iot": "8.7.13",
1004
- "manage": "8.6.14",
1005
- "monitor": "8.10.10",
1006
- "optimizer": "8.4.6",
1007
- "predict": "N/A",
1008
- "inspection": "8.8.4"
1009
1029
  }
1010
1030
  ]
1011
1031
 
@@ -1049,6 +1069,13 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
1049
1069
  self.manualCertificates()
1050
1070
 
1051
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
+
1052
1079
  self.displayInstallSummary()
1053
1080
 
1054
1081
  if not self.noConfirm:
@@ -1060,6 +1087,8 @@ class InstallApp(BaseApp, InstallSettingsMixin, InstallSummarizerMixin, ConfigGe
1060
1087
 
1061
1088
  # Prepare the namespace and launch the installation pipeline
1062
1089
  if self.noConfirm or continueWithInstall:
1090
+ self.createTektonFileWithDigest()
1091
+
1063
1092
  self.printH1("Launch Install")
1064
1093
  pipelinesNamespace = f"mas-{self.getParam('mas_instance_id')}-pipelines"
1065
1094