mas-cli 5.1.4__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 (114) hide show
  1. mas/cli/__init__.py +11 -0
  2. mas/cli/aiservice/install/__init__.py +11 -0
  3. mas/cli/aiservice/install/app.py +894 -0
  4. mas/cli/aiservice/install/argBuilder.py +180 -0
  5. mas/cli/aiservice/install/argParser.py +507 -0
  6. mas/cli/aiservice/install/params.py +100 -0
  7. mas/cli/aiservice/install/summarizer.py +134 -0
  8. mas/cli/cli.py +432 -0
  9. mas/cli/displayMixins.py +132 -0
  10. mas/cli/gencfg.py +113 -0
  11. mas/cli/install/__init__.py +11 -0
  12. mas/cli/install/app.py +1316 -0
  13. mas/cli/install/argBuilder.py +465 -0
  14. mas/cli/install/argParser.py +1176 -0
  15. mas/cli/install/catalogs.py +27 -0
  16. mas/cli/install/params.py +172 -0
  17. mas/cli/install/settings/__init__.py +23 -0
  18. mas/cli/install/settings/additionalConfigs.py +227 -0
  19. mas/cli/install/settings/db2Settings.py +252 -0
  20. mas/cli/install/settings/kafkaSettings.py +103 -0
  21. mas/cli/install/settings/manageSettings.py +273 -0
  22. mas/cli/install/settings/mongodbSettings.py +46 -0
  23. mas/cli/install/settings/turbonomicSettings.py +29 -0
  24. mas/cli/install/summarizer.py +398 -0
  25. mas/cli/templates/facilities-configs.yml.j2 +25 -0
  26. mas/cli/templates/ibm-mas-tekton.yaml +49772 -0
  27. mas/cli/templates/jdbccfg.yml.j2 +52 -0
  28. mas/cli/templates/pod-templates/best-effort/ibm-data-dictionary-assetdatadictionary.yml +26 -0
  29. mas/cli/templates/pod-templates/best-effort/ibm-mas-bascfg.yml +56 -0
  30. mas/cli/templates/pod-templates/best-effort/ibm-mas-coreidp.yml +21 -0
  31. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-actions.yml +28 -0
  32. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-auth.yml +32 -0
  33. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-datapower.yml +12 -0
  34. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-devops.yml +14 -0
  35. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-dm.yml +22 -0
  36. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-dsc.yml +40 -0
  37. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-edgeconfig.yml +10 -0
  38. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-fpl.yml +24 -0
  39. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-guardian.yml +20 -0
  40. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-iot.yml +10 -0
  41. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-mbgx.yml +18 -0
  42. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-mfgx.yml +14 -0
  43. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-monitor.yml +18 -0
  44. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-orgmgmt.yml +48 -0
  45. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-provision.yml +28 -0
  46. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-registry.yml +26 -0
  47. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-state.yml +40 -0
  48. mas/cli/templates/pod-templates/best-effort/ibm-mas-iot-webui.yml +22 -0
  49. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-healthextaccelerator.yml +13 -0
  50. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-healthextworkspace.yml +10 -0
  51. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-imagestitching.yml +10 -0
  52. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-manageaccelerators.yml +10 -0
  53. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-manageapp.yml +46 -0
  54. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-manageworkspace.yml +48 -0
  55. mas/cli/templates/pod-templates/best-effort/ibm-mas-manage-slackproxy.yml +10 -0
  56. mas/cli/templates/pod-templates/best-effort/ibm-mas-pushnotificationcfg.yml +13 -0
  57. mas/cli/templates/pod-templates/best-effort/ibm-mas-scimcfg.yml +14 -0
  58. mas/cli/templates/pod-templates/best-effort/ibm-mas-slscfg.yml +10 -0
  59. mas/cli/templates/pod-templates/best-effort/ibm-mas-smtpcfg.yml +10 -0
  60. mas/cli/templates/pod-templates/best-effort/ibm-mas-suite.yml +136 -0
  61. mas/cli/templates/pod-templates/best-effort/ibm-mas-visualinspection.yml +34 -0
  62. mas/cli/templates/pod-templates/best-effort/ibm-sls-licenseservice.yml +10 -0
  63. mas/cli/templates/pod-templates/guaranteed/ibm-data-dictionary-assetdatadictionary.yml +56 -0
  64. mas/cli/templates/pod-templates/guaranteed/ibm-mas-bascfg.yml +140 -0
  65. mas/cli/templates/pod-templates/guaranteed/ibm-mas-coreidp.yml +45 -0
  66. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-actions.yml +70 -0
  67. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-auth.yml +80 -0
  68. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-datapower.yml +24 -0
  69. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-devops.yml +26 -0
  70. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-dm.yml +52 -0
  71. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-dsc.yml +106 -0
  72. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-edgeconfig.yml +16 -0
  73. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-fpl.yml +62 -0
  74. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-guardian.yml +44 -0
  75. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-iot.yml +16 -0
  76. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-mbgx.yml +42 -0
  77. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-mfgx.yml +32 -0
  78. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-monitor.yml +42 -0
  79. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-orgmgmt.yml +126 -0
  80. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-provision.yml +70 -0
  81. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-registry.yml +62 -0
  82. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-state.yml +106 -0
  83. mas/cli/templates/pod-templates/guaranteed/ibm-mas-iot-webui.yml +52 -0
  84. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-healthextaccelerator.yml +28 -0
  85. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-healthextworkspace.yml +18 -0
  86. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-imagestitching.yml +16 -0
  87. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-manageaccelerators.yml +16 -0
  88. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-manageapp.yml +106 -0
  89. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-manageworkspace.yml +126 -0
  90. mas/cli/templates/pod-templates/guaranteed/ibm-mas-manage-slackproxy.yml +16 -0
  91. mas/cli/templates/pod-templates/guaranteed/ibm-mas-pushnotificationcfg.yml +25 -0
  92. mas/cli/templates/pod-templates/guaranteed/ibm-mas-scimcfg.yml +26 -0
  93. mas/cli/templates/pod-templates/guaranteed/ibm-mas-slscfg.yml +16 -0
  94. mas/cli/templates/pod-templates/guaranteed/ibm-mas-smtpcfg.yml +16 -0
  95. mas/cli/templates/pod-templates/guaranteed/ibm-mas-suite.yml +340 -0
  96. mas/cli/templates/pod-templates/guaranteed/ibm-mas-visualinspection.yml +76 -0
  97. mas/cli/templates/pod-templates/guaranteed/ibm-sls-licenseservice.yml +16 -0
  98. mas/cli/templates/suite_mongocfg.yml.j2 +55 -0
  99. mas/cli/uninstall/__init__.py +11 -0
  100. mas/cli/uninstall/app.py +197 -0
  101. mas/cli/uninstall/argParser.py +115 -0
  102. mas/cli/update/__init__.py +11 -0
  103. mas/cli/update/app.py +673 -0
  104. mas/cli/update/argParser.py +156 -0
  105. mas/cli/upgrade/__init__.py +11 -0
  106. mas/cli/upgrade/app.py +164 -0
  107. mas/cli/upgrade/argParser.py +68 -0
  108. mas/cli/upgrade/settings/__init__.py +19 -0
  109. mas/cli/validators.py +151 -0
  110. mas_cli-5.1.4.data/scripts/mas-cli +87 -0
  111. mas_cli-5.1.4.dist-info/METADATA +73 -0
  112. mas_cli-5.1.4.dist-info/RECORD +114 -0
  113. mas_cli-5.1.4.dist-info/WHEEL +5 -0
  114. mas_cli-5.1.4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,100 @@
1
+ # *****************************************************************************
2
+ # Copyright (c) 2024, 2025 IBM Corporation and other Contributors.
3
+ #
4
+ # All rights reserved. This program and the accompanying materials
5
+ # are made available under the terms of the Eclipse Public License v1.0
6
+ # which accompanies this distribution, and is available at
7
+ # http://www.eclipse.org/legal/epl-v10.html
8
+ #
9
+ # *****************************************************************************
10
+
11
+ requiredParams = [
12
+ # MAS
13
+ "mas_catalog_version",
14
+ # Storage classes
15
+ "storage_class_rwo",
16
+ "storage_class_rwx",
17
+ # Entitlement
18
+ "ibm_entitlement_key",
19
+ # DRO
20
+ "uds_contact_email",
21
+ "uds_contact_firstname",
22
+ "uds_contact_lastname"
23
+ ]
24
+
25
+ optionalParams = [
26
+ # Pipeline
27
+ "image_pull_policy",
28
+ "service_account_name",
29
+ # Catalogue
30
+ "mas_catalog_digest",
31
+ # SLS
32
+ "sls_namespace",
33
+ # DRO
34
+ "dro_namespace",
35
+ # Db2
36
+ "db2_action_system",
37
+ "db2_action_manage",
38
+ "db2_action_facilities",
39
+ "db2_type",
40
+ "db2_timezone",
41
+ "db2_namespace",
42
+ "db2_channel",
43
+ "db2_affinity_key",
44
+ "db2_affinity_value",
45
+ "db2_tolerate_key",
46
+ "db2_tolerate_value",
47
+ "db2_tolerate_effect",
48
+ "db2_cpu_requests",
49
+ "db2_cpu_limits",
50
+ "db2_memory_requests",
51
+ "db2_memory_limits",
52
+ "db2_backup_storage_size",
53
+ "db2_data_storage_size",
54
+ "db2_logs_storage_size",
55
+ "db2_meta_storage_size",
56
+ "db2_temp_storage_size",
57
+ # Dev Mode
58
+ "artifactory_username",
59
+ "artifactory_token",
60
+ # Aibroker
61
+ "aiservice_storage_provider",
62
+ "aiservice_storage_accesskey",
63
+ "aiservice_storage_secretkey",
64
+ "aiservice_storage_host",
65
+ "aiservice_storage_port",
66
+ "aiservice_storage_ssl",
67
+ "aiservice_storage_region",
68
+ "aiservice_storage_pipelines_bucket",
69
+ "aiservice_storage_tenants_bucket",
70
+ "aiservice_storage_templates_bucket",
71
+
72
+ "aiservice_watsonxai_apikey",
73
+ "aiservice_watsonxai_url",
74
+ "aiservice_watsonxai_project_id",
75
+ "aiservice_watsonx_action",
76
+
77
+ "aiservice_instance_id",
78
+
79
+ "minio_root_user",
80
+ "minio_root_password",
81
+
82
+ "tenant_entitlement_type",
83
+ "tenant_entitlement_start_date",
84
+ "tenant_entitlement_end_date",
85
+
86
+ "aiservice_s3_bucket_prefix",
87
+ "aiservice_s3_region",
88
+ "aiservice_s3_endpoint_url",
89
+ "aiservice_tenant_s3_bucket_prefix",
90
+ "aiservice_tenant_s3_region",
91
+ "aiservice_tenant_s3_endpoint_url",
92
+ "aiservice_tenant_s3_access_key",
93
+ "aiservice_tenant_s3_secret_key",
94
+
95
+ "rsl_url",
96
+ "rsl_org_id",
97
+ "rsl_token",
98
+
99
+ "environment_type",
100
+ ]
@@ -0,0 +1,134 @@
1
+ # *****************************************************************************
2
+ # Copyright (c) 2024, 2025 IBM Corporation and other Contributors.
3
+ #
4
+ # All rights reserved. This program and the accompanying materials
5
+ # are made available under the terms of the Eclipse Public License v1.0
6
+ # which accompanies this distribution, and is available at
7
+ # http://www.eclipse.org/legal/epl-v10.html
8
+ #
9
+ # *****************************************************************************
10
+
11
+ import logging
12
+ import yaml
13
+ from mas.devops.ocp import getConsoleURL
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class aiServiceInstallSummarizerMixin():
19
+ def ocpSummary(self) -> None:
20
+ self.printH2("Pipeline Configuration")
21
+ self.printParamSummary("Service Account", "service_account_name")
22
+ self.printParamSummary("Image Pull Policy", "image_pull_policy")
23
+ self.printSummary("Skip Pre-Install Healthcheck", "Yes" if self.getParam('skip_pre_check') == "true" else "No")
24
+
25
+ self.printH2("OpenShift Container Platform")
26
+ self.printSummary("Worker Node Architecture", self.architecture)
27
+ self.printSummary("Storage Class Provider", self.storageClassProvider)
28
+ self.printParamSummary("ReadWriteOnce Storage Class", "storage_class_rwo")
29
+ self.printParamSummary("ReadWriteMany Storage Class", "storage_class_rwx")
30
+
31
+ self.printParamSummary("Certificate Manager", "cert_manager_provider")
32
+ self.printParamSummary("Cluster Ingress Certificate Secret", "ocp_ingress_tls_secret_name")
33
+
34
+ def aiServiceSummary(self) -> None:
35
+ self.printH2("Maximo Operator Catalog")
36
+ self.printParamSummary("Catalog Version", "mas_catalog_version")
37
+ # We only list the digest if it's specified (primary use case is when running development builds in airgap environments)
38
+ if self.getParam("mas_catalog_digest" != ""):
39
+ self.printParamSummary("Catalog Digest", "mas_catalog_digest")
40
+
41
+ self.printH2("IBM Container Registry")
42
+ self.printParamSummary("IBM Entitled Registry", "mas_icr_cp")
43
+ self.printParamSummary("IBM Open Registry", "mas_icr_cpopen")
44
+
45
+ self.printH2("AI Service")
46
+ self.printParamSummary("Release", "aiservice_channel")
47
+ self.printParamSummary("Instance ID", "aiservice_instance_id")
48
+ self.printParamSummary("Environment type", "environment_type")
49
+
50
+ self.printH2("S3 Configuration")
51
+ self.printParamSummary("Storage provider", "aiservice_storage_provider")
52
+ if self.getParam("aiservice_storage_provider") == "minio":
53
+ self.printParamSummary("minio root username", "minio_root_user")
54
+ print()
55
+ self.printParamSummary("Storage access key", "aiservice_storage_accesskey")
56
+ self.printParamSummary("Storage host", "aiservice_storage_host")
57
+ self.printParamSummary("Storage port", "aiservice_storage_port")
58
+ self.printParamSummary("Storage ssl", "aiservice_storage_ssl")
59
+ self.printParamSummary("Storage region", "aiservice_storage_region")
60
+ self.printParamSummary("Storage pipelines bucket", "aiservice_storage_pipelines_bucket")
61
+ self.printParamSummary("Storage tenants bucket", "aiservice_storage_tenants_bucket")
62
+ self.printParamSummary("Storage templates bucket", "aiservice_storage_templates_bucket")
63
+ print()
64
+ self.printParamSummary("S3 bucket prefix", "aiservice_s3_bucket_prefix")
65
+ self.printParamSummary("S3 endpoint url", "aiservice_s3_endpoint_url")
66
+ self.printParamSummary("S3 bucket prefix (tenant level)", "aiservice_tenant_s3_bucket_prefix")
67
+ self.printParamSummary("S3 region (tenant level)", "aiservice_tenant_s3_region")
68
+ self.printParamSummary("S3 endpoint url (tenant level)", "aiservice_tenant_s3_endpoint_url")
69
+
70
+ self.printH2("IBM WatsonX")
71
+ self.printParamSummary("Watsonxai machine learning url", "aiservice_watsonxai_url")
72
+ self.printParamSummary("Watsonxai project id", "aiservice_watsonxai_project_id")
73
+
74
+ self.printH2("AI Service Tenant")
75
+ self.printParamSummary("Tenant entitlement type", "tenant_entitlement_type")
76
+ self.printParamSummary("Tenant start date", "tenant_entitlement_start_date")
77
+ self.printParamSummary("Tenant end date", "tenant_entitlement_end_date")
78
+
79
+ self.printH2("RSL")
80
+ self.printParamSummary("RSL url", "rsl_url")
81
+ self.printParamSummary("ORG Id of RSL", "rsl_org_id")
82
+ self.printParamSummary("Token for RSL", "rsl_token")
83
+
84
+ def db2Summary(self) -> None:
85
+ self.printH2("IBM Db2 Univeral Operator Configuration")
86
+ self.printParamSummary("Action", "db2_action_aiservice")
87
+ self.printParamSummary("Install Namespace", "db2_namespace")
88
+ self.printParamSummary("Subscription Channel", "db2_channel")
89
+
90
+ def droSummary(self) -> None:
91
+ self.printH2("IBM Data Reporter Operator (DRO) Configuration")
92
+ self.printParamSummary("Contact e-mail", "uds_contact_email")
93
+ self.printParamSummary("First name", "uds_contact_firstname")
94
+ self.printParamSummary("Last name", "uds_contact_lastname")
95
+ self.printParamSummary("Install Namespace", "dro_namespace")
96
+
97
+ def slsSummary(self) -> None:
98
+ self.printH2("IBM Suite License Service")
99
+ self.printParamSummary("Namespace", "sls_namespace")
100
+ if self.getParam("sls_action") == "install":
101
+ self.printSummary("Subscription Channel", "3.x")
102
+ self.printParamSummary("IBM Open Registry", "sls_icr_cpopen")
103
+ if self.slsLicenseFileLocal:
104
+ self.printSummary("License File", self.slsLicenseFileLocal)
105
+
106
+ def mongoSummary(self) -> None:
107
+ self.printH2("MongoDb")
108
+ if self.getParam("mongodb_action") == "install":
109
+ self.printSummary("Type", "MongoCE Operator")
110
+ self.printParamSummary("Install Namespace", "mongodb_namespace")
111
+ elif self.getParam("mongodb_action") == "byo":
112
+ self.printSummary("Type", "BYO (mongodb-system.yaml)")
113
+ else:
114
+ self.fatalError(f"Unexpected value for mongodb_action parameter: {self.getParam('mongodb_action')}")
115
+
116
+ def displayInstallSummary(self) -> None:
117
+ self.printH1("Review Settings")
118
+ self.printDescription([
119
+ "Connected to:",
120
+ f" - <u>{getConsoleURL(self.dynamicClient)}</u>"
121
+ ])
122
+
123
+ logger.debug("PipelineRun parameters:")
124
+ logger.debug(yaml.dump(self.params, default_flow_style=False))
125
+
126
+ # Cluster Config & AI Service
127
+ self.ocpSummary()
128
+ self.aiServiceSummary()
129
+
130
+ # Dependencies
131
+ self.droSummary()
132
+ self.slsSummary()
133
+ self.mongoSummary()
134
+ self.db2Summary()
mas/cli/cli.py ADDED
@@ -0,0 +1,432 @@
1
+ # *****************************************************************************
2
+ # Copyright (c) 2024 IBM Corporation and other Contributors.
3
+ #
4
+ # All rights reserved. This program and the accompanying materials
5
+ # are made available under the terms of the Eclipse Public License v1.0
6
+ # which accompanies this distribution, and is available at
7
+ # http://www.eclipse.org/legal/epl-v10.html
8
+ #
9
+ # *****************************************************************************
10
+
11
+ import logging
12
+ import urllib3
13
+
14
+ from argparse import RawTextHelpFormatter
15
+ from shutil import which
16
+ from os import path, environ
17
+ from sys import exit
18
+ from subprocess import PIPE, Popen, TimeoutExpired
19
+ import threading
20
+ import json
21
+
22
+ # Use of the openshift client rather than the kubernetes client allows us access to "apply"
23
+ from kubernetes import config
24
+ from kubernetes.client import api_client, Configuration
25
+ from openshift.dynamic import DynamicClient
26
+ from openshift.dynamic.exceptions import NotFoundError
27
+
28
+ from prompt_toolkit import prompt, print_formatted_text, HTML
29
+
30
+ from mas.devops.mas import isAirgapInstall
31
+ from mas.devops.ocp import connect, isSNO, getNodes
32
+
33
+ from .displayMixins import PrintMixin, PromptMixin
34
+
35
+ # Configure the logger
36
+ logger = logging.getLogger(__name__)
37
+
38
+ # Disable warnings when users are connecting to OCP clusters with self-signed certificates
39
+ urllib3.disable_warnings()
40
+
41
+
42
+ def getHelpFormatter(formatter=RawTextHelpFormatter, w=160, h=50):
43
+ """
44
+ Return a wider HelpFormatter, if possible.
45
+
46
+ https://stackoverflow.com/a/57655311
47
+ """
48
+ try:
49
+ kwargs = {'width': w, 'max_help_position': h}
50
+ formatter(None, **kwargs)
51
+ return lambda prog: formatter(prog, **kwargs)
52
+ except TypeError:
53
+ logger.warning("argparse help formatter failed, falling back.")
54
+ return formatter
55
+
56
+
57
+ class RunCmdResult(object):
58
+ def __init__(self, returnCode, output, error):
59
+ self.rc = returnCode
60
+ self.out = output
61
+ self.err = error
62
+
63
+ def successful(self):
64
+ return self.rc == 0
65
+
66
+ def failed(self):
67
+ return self.rc != 0
68
+
69
+
70
+ def runCmd(cmdArray, timeout=630):
71
+ """
72
+ Run a command on the local host. This drives all the helm operations,
73
+ as there is no python Helm client available.
74
+ # Parameters
75
+ cmdArray (list<string>): Command to execute
76
+ timeout (int): How long to allow for the command to complete
77
+ # Returns
78
+ [int, string, string]: `returnCode`, `stdOut`, `stdErr`
79
+ """
80
+
81
+ lock = threading.Lock()
82
+
83
+ with lock:
84
+ p = Popen(cmdArray, stdin=PIPE, stdout=PIPE, stderr=PIPE, bufsize=-1)
85
+ try:
86
+ output, error = p.communicate(timeout=timeout)
87
+ return RunCmdResult(p.returncode, output, error)
88
+ except TimeoutExpired as e:
89
+ return RunCmdResult(127, 'TimeoutExpired', str(e))
90
+
91
+
92
+ def logMethodCall(func):
93
+ def wrapper(self, *args, **kwargs):
94
+ logger.debug(f">>> BaseApp.{func.__name__}")
95
+ result = func(self, *args, **kwargs)
96
+ logger.debug(f"<<< BaseApp.{func.__name__}")
97
+ return result
98
+ return wrapper
99
+
100
+
101
+ class BaseApp(PrintMixin, PromptMixin):
102
+ def __init__(self):
103
+ # Set up a log formatter
104
+ chFormatter = logging.Formatter('%(asctime)-25s' + ' %(levelname)-8s %(message)s')
105
+
106
+ # Set up a log handler (5mb rotating log file)
107
+ ch = logging.handlers.RotatingFileHandler(
108
+ "mas.log", maxBytes=(1048576 * 5), backupCount=2
109
+ )
110
+ ch.setLevel(logging.DEBUG)
111
+ ch.setFormatter(chFormatter)
112
+
113
+ # Configure the root logger
114
+ rootLogger = logging.getLogger()
115
+ rootLogger.addHandler(ch)
116
+ rootLogger.setLevel(logging.DEBUG)
117
+ logging.getLogger('asyncio').setLevel(logging.INFO)
118
+
119
+ # Supports extended semver, unlike mas.cli.__version__
120
+ self.version = "5.1.4"
121
+ self.h1count = 0
122
+ self.h2count = 0
123
+
124
+ self.localConfigDir = None
125
+ self.templatesDir = path.join(path.abspath(path.dirname(__file__)), "templates")
126
+ self.tektonDefsWithoutDigestPath = path.join(self.templatesDir, "ibm-mas-tekton.yaml")
127
+ self.tektonDefsWithDigestPath = path.join(self.templatesDir, "ibm-mas-tekton-with-digest.yaml")
128
+
129
+ # Default to using the tekton definitions without image digests
130
+ self.tektonDefsPath = self.tektonDefsWithoutDigestPath
131
+
132
+ # Initialize the dictionary that will hold the parameters we pass to a PipelineRun
133
+ self.params = dict()
134
+
135
+ # These dicts will hold the additional-configs, pod-templates, sls license file and manual certificates secrets
136
+ self.additionalConfigsSecret = None
137
+ self.podTemplatesSecret = None
138
+ self.slsLicenseFileSecret = None
139
+ self.certsSecret = None
140
+
141
+ self._isSNO = None
142
+ self._isAirgap = None
143
+
144
+ # Until we connect to the cluster we don't know what architecture it's worker nodes are
145
+ self.architecture = None
146
+
147
+ self.compatibilityMatrix = {
148
+ "9.1.x": {
149
+ "facilities": ["9.1.x"],
150
+ "assist": ["9.1.x", "9.0.x"],
151
+ "iot": ["9.1.x", "9.0.x"],
152
+ "manage": ["9.1.x", "9.0.x"],
153
+ "monitor": ["9.1.x", "9.0.x"],
154
+ "optimizer": ["9.1.x", "9.0.x"],
155
+ "predict": ["9.1.x", "9.0.x"],
156
+ "visualinspection": ["9.1.x", "9.0.x"],
157
+ "aibroker": ["9.1.x", "9.0.x"],
158
+
159
+ },
160
+ "9.1.x-feature": {
161
+ "assist": ["9.0.x"],
162
+ "iot": ["9.0.x"],
163
+ "manage": ["9.1.x-feature", "9.0.x"],
164
+ "monitor": ["9.0.x"],
165
+ "optimizer": ["9.1.x-feature", "9.0.x"],
166
+ "predict": ["9.0.x"],
167
+ "visualinspection": ["9.1.x-feature", "9.0.x"],
168
+ "aibroker": ["9.0.x"],
169
+ },
170
+ "9.0.x": {
171
+ "assist": ["9.0.x", "8.8.x"],
172
+ "iot": ["9.0.x", "8.8.x"],
173
+ "manage": ["9.0.x", "8.7.x"],
174
+ "monitor": ["9.0.x", "8.11.x"],
175
+ "optimizer": ["9.0.x", "8.5.x"],
176
+ "predict": ["9.0.x", "8.9.x"],
177
+ "visualinspection": ["9.0.x", "8.9.x"],
178
+ "aibroker": ["9.0.x"],
179
+ },
180
+ "8.11.x": {
181
+ "assist": ["8.8.x", "8.7.x"],
182
+ "iot": ["8.8.x", "8.7.x"],
183
+ "manage": ["8.7.x", "8.6.x"],
184
+ "monitor": ["8.11.x", "8.10.x"],
185
+ "optimizer": ["8.5.x", "8.4.x"],
186
+ "predict": ["8.9.x", "8.8.x"],
187
+ "visualinspection": ["8.9.x", "8.8.x"],
188
+ },
189
+ "8.10.x": {
190
+ "assist": ["8.7.x", "8.6.x"],
191
+ "hputilities": ["8.6.x", "8.5.x"],
192
+ "iot": ["8.7.x", "8.6.x"],
193
+ "manage": ["8.6.x", "8.5.x"],
194
+ "monitor": ["8.10.x", "8.9.x"],
195
+ "optimizer": ["8.4.x", "8.3.x"],
196
+ "predict": ["8.8.x", "8.7.x"],
197
+ "visualinspection": ["8.8.x", "8.7.x"],
198
+ },
199
+ }
200
+
201
+ self.licenses = {
202
+ "8.9.x": " - <u>https://ibm.biz/MAS89-License</u>",
203
+ "8.10.x": " - <u>https://ibm.biz/MAS810-License</u>",
204
+ "8.11.x": " - <u>https://ibm.biz/MAS811-License</u>\n - <u>https://ibm.biz/MAXIT81-License</u>",
205
+ "9.0.x": " - <u>https://ibm.biz/MAS90-License</u>\n - <u>https://ibm.biz/MaximoIT90-License</u>\n - <u>https://ibm.biz/MAXArcGIS90-License</u>",
206
+ "9.1.x-feature": " - <u>https://ibm.biz/MAS90-License</u>\n - <u>https://ibm.biz/MaximoIT90-License</u>\n - <u>https://ibm.biz/MAXArcGIS90-License</u>\n\nBe aware, this channel subscription is supported for non-production use only. \nIt allows early access to new features for evaluation in non-production environments. \nThis subscription is offered alongside and in parallel with our normal maintained streams. \nWhen using this subscription, IBM Support will only accept cases for the latest available bundle deployed in a non-production environment. \nSeverity must be either 3 or 4 and cases cannot be escalated. \nPlease refer to IBM documentation for more details.\n",
207
+ "9.1.x": " - <u>https://ibm.biz/MAS91-License</u>\n - <u>https://ibm.biz/MAXIT91-License</u>\n - <u>https://ibm.biz/MAXESRI91-License</u>",
208
+ "aibroker-9.1.x": " - <u>https://ibm.biz/MAS91-License</u>",
209
+ }
210
+
211
+ self.upgrade_path = {
212
+ "9.1.x": "9.1.x",
213
+ "9.1.x-feature": "9.1.x",
214
+ "9.0.x": "9.1.x",
215
+ "8.11.x": "9.0.x",
216
+ "8.10.x": "8.11.x",
217
+ "8.9.x": "8.10.x",
218
+ }
219
+
220
+ self.spinner = {
221
+ "interval": 80,
222
+ "frames": [" ⠋", " ⠙", " ⠹", " ⠸", " ⠼", " ⠴", " ⠦", " ⠧", " ⠇", " ⠏"]
223
+ }
224
+ self.successIcon = "✅️"
225
+ self.failureIcon = "❌"
226
+
227
+ self._dynClient = None
228
+
229
+ self.printTitle(f"\nIBM Maximo Application Suite Admin CLI v{self.version}")
230
+ print_formatted_text(HTML("Powered by <Orange><u>https://github.com/ibm-mas/ansible-devops/</u></Orange> and <Orange><u>https://tekton.dev/</u></Orange>\n"))
231
+ if which("kubectl") is None:
232
+ self.fatalError("Could not find kubectl on the path, see <Orange><u>https://kubernetes.io/docs/tasks/tools/#kubectl</u></Orange> for installation instructions")
233
+
234
+ @logMethodCall
235
+ def createTektonFileWithDigest(self) -> None:
236
+ if path.exists(self.tektonDefsWithDigestPath):
237
+ logger.debug(f"We have already generated {self.tektonDefsWithDigestPath}")
238
+ elif isAirgapInstall(self.dynamicClient):
239
+ # We need to modify the tekton definitions to
240
+ imageWithoutDigest = f"quay.io/ibmmas/cli:{self.version}"
241
+ self.printH1("Disconnected OpenShift Preparation")
242
+ self.printDescription([
243
+ f"Unless the {imageWithoutDigest} image is accessible from your cluster the MAS CLI container image must be present in your mirror registry"
244
+ ])
245
+ cmdArray = ["skopeo", "inspect", f"docker://{imageWithoutDigest}"]
246
+ logger.info(f"Skopeo inspect command: {' '.join(cmdArray)}")
247
+ skopeoResult = runCmd(cmdArray)
248
+ if skopeoResult.successful():
249
+ skopeoData = json.loads(skopeoResult.out)
250
+ logger.info(f"Skopeo Data for {imageWithoutDigest}: {skopeoData}")
251
+ if "Digest" not in skopeoData:
252
+ self.fatalError("Recieved bad data inspecting CLI manifest to determine digest")
253
+ cliImageDigest = skopeoData["Digest"]
254
+ else:
255
+ warning = f"Unable to retrieve image digest for {imageWithoutDigest} ({skopeoResult.rc})"
256
+ self.printWarning(warning)
257
+ logger.warning(warning)
258
+ logger.warning(skopeoResult.err)
259
+ if self.noConfirm:
260
+ self.fatalError("Unable to automatically determine CLI image digest and --no-confirm flag has been set")
261
+ else:
262
+ cliImageDigest = self.promptForString(f"Enter {imageWithoutDigest} image digest")
263
+
264
+ # Overwrite the tekton definitions with one that uses the looked up image digest
265
+ imageWithDigest = f"quay.io/ibmmas/cli@{cliImageDigest}"
266
+ self.printHighlight(f"\nConverting Tekton definitions to use {imageWithDigest}")
267
+ with open(self.tektonDefsPath, 'r') as file:
268
+ tektonDefsWithoutDigest = file.read()
269
+
270
+ tektonDefsWithDigest = tektonDefsWithoutDigest.replace(imageWithoutDigest, imageWithDigest)
271
+
272
+ with open(self.tektonDefsWithDigestPath, 'w') as file:
273
+ file.write(tektonDefsWithDigest)
274
+
275
+ self.tektonDefsPath = self.tektonDefsWithDigestPath
276
+
277
+ @logMethodCall
278
+ def getCompatibleVersions(self, coreChannel: str, appId: str) -> list:
279
+ if coreChannel in self.compatibilityMatrix:
280
+ return self.compatibilityMatrix[coreChannel][appId]
281
+ else:
282
+ return []
283
+
284
+ @logMethodCall
285
+ def fatalError(self, message: str, exception: Exception = None) -> None:
286
+ if exception is not None:
287
+ logger.error(message)
288
+ logger.exception(exception, stack_info=True)
289
+ print_formatted_text(HTML(f"<Red>Fatal Exception: {message.replace(' & ', ' &amp; ')}: {exception}</Red>\n"))
290
+ else:
291
+ logger.error(message)
292
+ print_formatted_text(HTML(f"<Red>Fatal Error: {message.replace(' & ', ' &amp; ')}</Red>\n"))
293
+ exit(1)
294
+
295
+ @logMethodCall
296
+ def isSNO(self):
297
+ if self._isSNO is None:
298
+ self._isSNO = isSNO(self.dynamicClient)
299
+ return self._isSNO
300
+
301
+ @logMethodCall
302
+ def isAirgap(self):
303
+ if self._isAirgap is None:
304
+ # First check if the legacy ICSP is installed. If it is raise an error and instruct the user to re-run configure-airgap to
305
+ # migrate the cluster from ICSP to IDMS
306
+ if isAirgapInstall(self.dynamicClient, checkICSP=True):
307
+ self.fatalError("Deprecated Maximo Application Suite ImageContentSourcePolicy detected on the target cluster. Run 'mas configure-airgap' to migrate to the replacement ImageDigestMirrorSet beofre proceeding.")
308
+ self._isAirgap = isAirgapInstall(self.dynamicClient)
309
+ return self._isAirgap
310
+
311
+ def setParam(self, param: str, value: str):
312
+ self.params[param] = value
313
+
314
+ def getParam(self, param: str):
315
+ """
316
+ Returns the value of a parameter, or an empty string is the parameter has not set at all or is set to None
317
+ """
318
+ if param in self.params and self.params[param] is not None:
319
+ return self.params[param]
320
+ else:
321
+ return ""
322
+
323
+ @property
324
+ def dynamicClient(self):
325
+ if self._dynClient is not None:
326
+ return self._dynClient
327
+ else:
328
+ return self.reloadDynamicClient()
329
+
330
+ @logMethodCall
331
+ def reloadDynamicClient(self):
332
+ """
333
+ Configure the Kubernetes API Client using the active context in kubeconfig
334
+ """
335
+ logger.debug("Reloading Kubernetes Client Configuration")
336
+ try:
337
+ if "KUBERNETES_SERVICE_HOST" in environ:
338
+ config.load_incluster_config()
339
+ k8s_config = Configuration.get_default_copy()
340
+ self._apiClient = api_client.ApiClient(configuration=k8s_config)
341
+ self._dynClient = DynamicClient(self._apiClient)
342
+ else:
343
+ config.load_kube_config()
344
+ self._apiClient = api_client.ApiClient()
345
+ self._dynClient = DynamicClient(self._apiClient)
346
+ return self._dynClient
347
+ except Exception as e:
348
+ logger.warning(f"Error: Unable to connect to OpenShift Container Platform: {e}")
349
+ logger.exception(e, stack_info=True)
350
+ return None
351
+
352
+ @logMethodCall
353
+ def connect(self):
354
+ promptForNewServer = False
355
+ self.reloadDynamicClient()
356
+ if self._dynClient is not None:
357
+ try:
358
+ routesAPI = self._dynClient.resources.get(api_version="route.openshift.io/v1", kind="Route")
359
+ consoleRoute = routesAPI.get(name="console", namespace="openshift-console")
360
+ print_formatted_text(HTML(f"Already connected to OCP Cluster:\n <u><Orange>https://{consoleRoute.spec.host}</Orange></u>"))
361
+ print()
362
+ if not self.noConfirm:
363
+ # We are already connected to a cluster, but prompt the user if they want to use this connection
364
+ promptForNewServer = not self.yesOrNo("Proceed with this cluster")
365
+ except Exception as e:
366
+ # We are already connected to a cluster, but the connection is not valid so prompt for connection details
367
+ logger.debug("Failed looking up OpenShift Console route to verify connection")
368
+ logger.exception(e, stack_info=True)
369
+ promptForNewServer = True
370
+ else:
371
+ # We are not already connected to any cluster, so prompt for connection details
372
+ promptForNewServer = True
373
+
374
+ if promptForNewServer:
375
+ # Prompt for new connection properties
376
+ server = prompt(HTML('<Yellow>Server URL:</Yellow> '), placeholder="https://...")
377
+ token = prompt(HTML('<Yellow>Login Token:</Yellow> '), is_password=True, placeholder="sha256~...")
378
+ skipVerify = self.yesOrNo('Disable TLS Verify')
379
+ connect(server, token, skipVerify)
380
+ self.reloadDynamicClient()
381
+ if self._dynClient is None:
382
+ print_formatted_text(HTML("<Red>Unable to connect to cluster. See log file for details</Red>"))
383
+ exit(1)
384
+
385
+ # Now that we are connected, inspect the architecture of the OpenShift cluster
386
+ self.lookupTargetArchitecture()
387
+
388
+ @logMethodCall
389
+ def lookupTargetArchitecture(self, architecture: str = None) -> None:
390
+ logger.debug("Looking up worker node architecture")
391
+ if architecture is not None:
392
+ self.architecture = architecture
393
+ logger.debug(f"Target architecture (overridden): {self.architecture}")
394
+ else:
395
+ nodes = getNodes(self.dynamicClient)
396
+ self.architecture = nodes[0]["status"]["nodeInfo"]["architecture"]
397
+ logger.debug(f"Target architecture: {self.architecture}")
398
+
399
+ if self.architecture not in ["amd64", "s390x", "ppc64le"]:
400
+ self.fatalError(f"Unsupported worker node architecture: {self.architecture}")
401
+
402
+ @logMethodCall
403
+ def initializeApprovalConfigMap(self, namespace: str, id: str, enabled: bool, maxRetries: int = 100, delay: int = 300, ignoreFailure: bool = True) -> None:
404
+ """
405
+ Set key = None if you don't want approval workflow enabled
406
+ """
407
+ cmAPI = self.dynamicClient.resources.get(api_version="v1", kind="ConfigMap")
408
+ configMap = {
409
+ "apiVersion": "v1",
410
+ "kind": "ConfigMap",
411
+ "metadata": {
412
+ "name": f"approval-{id}",
413
+ "namespace": namespace
414
+ },
415
+ "data": {
416
+ "MAX_RETRIES": str(maxRetries),
417
+ "DELAY": str(delay),
418
+ "IGNORE_FAILURE": str(ignoreFailure),
419
+ "STATUS": ""
420
+ }
421
+ }
422
+
423
+ # Delete any existing configmap and create a new one
424
+ try:
425
+ logger.debug(f"Deleting any existing approval workflow configmap for {id}")
426
+ cmAPI.delete(name=f"approval-{id}", namespace=namespace)
427
+ except NotFoundError:
428
+ pass
429
+
430
+ if enabled:
431
+ logger.debug(f"Enabling approval workflow for {id} with {maxRetries} max retries on a {delay}s delay ({'ignoring failures' if ignoreFailure else 'abort on failure'})")
432
+ cmAPI.create(body=configMap, namespace=namespace)