mas-cli 9.5.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 ADDED
@@ -0,0 +1,11 @@
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
+ __version__ = "9.5.0"
mas/cli/cli.py ADDED
@@ -0,0 +1,189 @@
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
+ from argparse import RawTextHelpFormatter
11
+ from shutil import which
12
+ from os import path
13
+
14
+ # Use of the openshift client rather than the kubernetes client allows us access to "apply"
15
+ from openshift import dynamic
16
+ from kubernetes import config
17
+ from kubernetes.client import api_client
18
+
19
+ from prompt_toolkit import prompt, print_formatted_text, HTML
20
+ from prompt_toolkit.validation import Validator, ValidationError
21
+
22
+ # Available named colours in prompt_toolkit
23
+ # -----------------------------------------------------------------------------
24
+ # AliceBlue AntiqueWhite Aqua Aquamarine Azure Beige Bisque Black BlanchedAlmond Blue BlueViolet
25
+ # Brown BurlyWood CadetBlue Chartreuse Chocolate Coral CornflowerBlue Cornsilk Crimson Cyan
26
+ # DarkBlue DarkCyan DarkGoldenRod DarkGray DarkGreen DarkGrey DarkKhaki DarkMagenta DarkOliveGreen
27
+ # DarkOrange DarkOrchid DarkRed DarkSalmon DarkSeaGreen DarkSlateBlue DarkSlateGray DarkSlateGrey
28
+ # DarkTurquoise DarkViolet DeepPink DeepSkyBlue DimGray DimGrey DodgerBlue FireBrick FloralWhite
29
+ # ForestGreen Fuchsia Gainsboro GhostWhite Gold GoldenRod Gray Green GreenYellow Grey HoneyDew
30
+ # HotPink IndianRed Indigo Ivory Khaki Lavender LavenderBlush LawnGreen LemonChiffon LightBlue
31
+ # LightCoral LightCyan LightGoldenRodYellow LightGray LightGreen LightGrey LightPink LightSalmon
32
+ # LightSeaGreen LightSkyBlue LightSlateGray LightSlateGrey LightSteelBlue LightYellow Lime
33
+ # LimeGreen Linen Magenta Maroon MediumAquaMarine MediumBlue MediumOrchid MediumPurple MediumSeaGreen
34
+ # MediumSlateBlue MediumSpringGreen MediumTurquoise MediumVioletRed MidnightBlue MintCream MistyRose
35
+ # Moccasin NavajoWhite Navy OldLace Olive OliveDrab Orange OrangeRed Orchid PaleGoldenRod PaleGreen
36
+ # PaleTurquoise PaleVioletRed PapayaWhip PeachPuff Peru Pink Plum PowderBlue Purple RebeccaPurple
37
+ # Red RosyBrown RoyalBlue SaddleBrown Salmon SandyBrown SeaGreen SeaShell Sienna Silver SkyBlue
38
+ # SlateBlue SlateGray SlateGrey Snow SpringGreen SteelBlue Tan Teal Thistle Tomato Turquoise
39
+ # Violet Wheat White WhiteSmoke Yellow YellowGreen
40
+
41
+ from mas.cli import __version__ as packageVersion
42
+ from mas.devops.ocp import connect
43
+ from mas.devops.mas import verifyMasInstance
44
+
45
+ from sys import exit
46
+
47
+ import logging
48
+
49
+ logger = logging.getLogger(__name__)
50
+
51
+
52
+ class InstanceIDValidator(Validator):
53
+ def validate(self, document):
54
+ """
55
+ Validate that a MAS instance ID exists on the target cluster
56
+ """
57
+ instanceId = document.text
58
+
59
+ dynClient = dynamic.DynamicClient(
60
+ api_client.ApiClient(configuration=config.load_kube_config())
61
+ )
62
+ if not verifyMasInstance(dynClient, instanceId):
63
+ raise ValidationError(message='Not a valid MAS instance ID on this cluster', cursor_position=len(instanceId))
64
+
65
+
66
+ class YesNoValidator(Validator):
67
+ def validate(self, document):
68
+ """
69
+ Validate that a response is understandable as a yes/no response
70
+ """
71
+ response = document.text
72
+ if response.lower() not in ["y", "n", "yes", "no"]:
73
+ raise ValidationError(message='Enter a valid response: y(es), n(o)', cursor_position=len(response))
74
+
75
+
76
+ def getHelpFormatter(formatter=RawTextHelpFormatter, w=160, h=50):
77
+ """
78
+ Return a wider HelpFormatter, if possible.
79
+
80
+ https://stackoverflow.com/a/57655311
81
+ """
82
+ try:
83
+ kwargs = {'width': w, 'max_help_position': h}
84
+ formatter(None, **kwargs)
85
+ return lambda prog: formatter(prog, **kwargs)
86
+ except TypeError:
87
+ logger.warn("argparse help formatter failed, falling back.")
88
+ return formatter
89
+
90
+
91
+ class BaseApp(object):
92
+ def __init__(self):
93
+ # Set up a log formatter
94
+ chFormatter = logging.Formatter('%(asctime)-25s' + ' %(levelname)-8s %(message)s')
95
+
96
+ # Set up a log handler (5mb rotating log file)
97
+ ch = logging.handlers.RotatingFileHandler(
98
+ "mas.log", maxBytes=(1048576*5), backupCount=2
99
+ )
100
+ ch.setLevel(logging.DEBUG)
101
+ ch.setFormatter(chFormatter)
102
+
103
+ # Configure the root logger
104
+ rootLogger = logging.getLogger()
105
+ rootLogger.addHandler(ch)
106
+ rootLogger.setLevel(logging.DEBUG)
107
+
108
+ self.version = packageVersion
109
+ self.h1count = 0
110
+
111
+ self.tektonDefsPath = path.join(path.abspath(path.dirname(__file__)), "templates", "ibm-mas-tekton.yaml")
112
+ print(self.tektonDefsPath)
113
+
114
+ self.spinner = {
115
+ "interval": 80,
116
+ "frames": [" ⠋", " ⠙", " ⠹", " ⠸", " ⠼", " ⠴", " ⠦", " ⠧", " ⠇", " ⠏"]
117
+ }
118
+ self.successIcon = "✅️"
119
+ self.failureIcon = "❌"
120
+
121
+ self._dynClient = None
122
+
123
+ self.printTitle(f"IBM Maximo Application Suite Admin CLI v{self.version}")
124
+ print_formatted_text(HTML("Powered by <DarkGoldenRod><u>https://github.com/ibm-mas/ansible-devops/</u></DarkGoldenRod> and <DarkGoldenRod><u>https://tekton.dev/</u></DarkGoldenRod>"))
125
+
126
+ if which("kubectl") is None:
127
+ logger.error("Could not find kubectl on the path")
128
+ print_formatted_text(HTML("\n<Red>Error: Could not find kubectl on the path, see <u>https://kubernetes.io/docs/tasks/tools/#kubectl</u> for installation instructions</Red>\n"))
129
+ exit(1)
130
+
131
+ def printTitle(self, message):
132
+ print_formatted_text(HTML(f"<b><u>{message}</u></b>"))
133
+
134
+ def printH1(self, message):
135
+ self.h1count += 1
136
+ print()
137
+ print_formatted_text(HTML(f"<u><SteelBlue>{self.h1count}. {message}</SteelBlue></u>"))
138
+
139
+ @property
140
+ def dynamicClient(self):
141
+ if self._dynClient is not None:
142
+ return self._dynClient
143
+ else:
144
+ return self.reloadDynamicClient()
145
+
146
+ def reloadDynamicClient(self):
147
+ """
148
+ Configure the Kubernetes API Client using the active context in kubeconfig
149
+ """
150
+ logger.debug("Reloading Kubernetes Client Configuration")
151
+ try:
152
+ config.load_kube_config()
153
+ self._dynClient = dynamic.DynamicClient(
154
+ api_client.ApiClient(configuration=config.load_kube_config())
155
+ )
156
+ return self._dynClient
157
+ except Exception as e:
158
+ logger.warning(f"Error: Unable to connect to OpenShift Container Platform: {e}")
159
+ return None
160
+
161
+ def connect(self, noConfirm):
162
+ promptForNewServer = False
163
+ self.reloadDynamicClient()
164
+ if self._dynClient is not None:
165
+ try:
166
+ routesAPI = self._dynClient.resources.get(api_version="route.openshift.io/v1", kind="Route")
167
+ consoleRoute = routesAPI.get(name="console", namespace="openshift-console")
168
+ print_formatted_text(HTML(f"Already connected to OCP Cluster:\n <u><Orange>https://{consoleRoute.spec.host}</Orange></u>"))
169
+ print()
170
+ if not noConfirm:
171
+ # We are already connected to a cluster, but prompt the user if they want to use this connection
172
+ continueWithExistingCluster = prompt(HTML('<Yellow>Proceed with this cluster?</Yellow> '), validator=YesNoValidator(), validate_while_typing=False)
173
+ promptForNewServer = continueWithExistingCluster in ["n", "no"]
174
+ except Exception:
175
+ # We are already connected to a cluster, but the connection is not valid so prompt for connection details
176
+ promptForNewServer = True
177
+ else:
178
+ # We are not already connected to any cluster, so prompt for connection details
179
+ promptForNewServer = True
180
+
181
+ if promptForNewServer:
182
+ # Prompt for new connection properties
183
+ server = prompt(HTML('<Yellow>Server URL:</Yellow> '), placeholder="https://...")
184
+ token = prompt(HTML('<Yellow>Login Token:</Yellow> '), is_password=True, placeholder="sha256~...")
185
+ connect(server, token)
186
+ self.reloadDynamicClient()
187
+ if self._dynClient is None:
188
+ print_formatted_text(HTML("<Red>Unable to connect to cluster. See log file for details</Red>"))
189
+ exit(1)
@@ -0,0 +1,281 @@
1
+ #!python
2
+ # *****************************************************************************
3
+ # Copyright (c) 2024 IBM Corporation and other Contributors.
4
+ #
5
+ # All rights reserved. This program and the accompanying materials
6
+ # are made available under the terms of the Eclipse Public License v1.0
7
+ # which accompanies this distribution, and is available at
8
+ # http://www.eclipse.org/legal/epl-v10.html
9
+ #
10
+ # *****************************************************************************
11
+
12
+ import argparse
13
+ import sys
14
+ import logging
15
+ import logging.handlers
16
+ from prompt_toolkit import prompt, print_formatted_text, HTML
17
+ from prompt_toolkit.completion import WordCompleter
18
+
19
+ from halo import Halo
20
+
21
+ from mas.cli import __version__ as packageVersion
22
+ from mas.cli.cli import InstanceIDValidator, YesNoValidator, BaseApp, getHelpFormatter
23
+ from mas.devops.ocp import createNamespace
24
+ from mas.devops.mas import listMasInstances, verifyMasInstance
25
+ from mas.devops.tekton import installOpenShiftPipelines, updateTektonDefinitions, launchUninstallPipeline
26
+ from openshift.dynamic.exceptions import NotFoundError
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ class App(BaseApp):
31
+ def uninstall(self, args):
32
+ """
33
+ Uninstall MAS instance
34
+ """
35
+ instanceId = args.instance_id
36
+ noConfirm = args.no_confirm
37
+ if args.uninstall_all_deps:
38
+ uninstallGrafana = True
39
+ uninstallIBMCatalog = True
40
+ uninstallCommonServices = True
41
+ uninstallCertManager = True
42
+ uninstallUDS = True
43
+ uninstallMongoDb = True
44
+ uninstallSLS = True
45
+ else:
46
+ uninstallGrafana = args.uninstall_grafana
47
+ uninstallIBMCatalog = args.uninstall_ibm_catalog
48
+ uninstallCommonServices = args.uninstall_common_services
49
+ uninstallCertManager = args.uninstall_cert_manager
50
+ uninstallUDS = args.uninstall_uds
51
+ uninstallMongoDb = args.uninstall_mongodb
52
+ uninstallSLS = args.uninstall_sls
53
+
54
+ if instanceId is None:
55
+ self.printH1("Set Target OpenShift Cluster")
56
+ # Connect to the target cluster
57
+ self.connect(noConfirm)
58
+ else:
59
+ logger.debug("MAS instance ID is set, so we assume already connected to the desired OCP")
60
+
61
+ if self.dynamicClient is None:
62
+ print_formatted_text(HTML("<Red>Error: The Kubernetes dynamic Client is not available. See log file for details</Red>"))
63
+ sys.exit(1)
64
+
65
+ if instanceId is None:
66
+ # Interactive mode
67
+ self.printH1("Instance Selection")
68
+ print_formatted_text(HTML("<LightSlateGrey>Select a MAS instance to uninstall from the list below:</LightSlateGrey>"))
69
+ suites = listMasInstances(self.dynamicClient)
70
+ suiteOptions = []
71
+ for suite in suites:
72
+ print_formatted_text(HTML(f"- <u>{suite['metadata']['name']}</u> v{suite['status']['versions']['reconciled']}"))
73
+ suiteOptions.append(suite['metadata']['name'])
74
+
75
+ suiteCompleter = WordCompleter(suiteOptions)
76
+ print()
77
+ instanceId = prompt(HTML(f'<Yellow>Enter MAS instance ID: </Yellow>'), completer=suiteCompleter, validator=InstanceIDValidator(), validate_while_typing=False)
78
+
79
+ self.printH1("Uninstall MAS Dependencies")
80
+ uninstallCertManager = prompt(HTML(f'<Yellow>Uninstall Certificate Manager? </Yellow>'), validator=YesNoValidator()) in ["y", "yes"]
81
+ if uninstallCertManager:
82
+ # If you choose to uninstall Cert-Manager, everything will be uninstalled
83
+ uninstallGrafana = True
84
+ uninstallIBMCatalog = True
85
+ uninstallCommonServices = True
86
+ uninstallUDS = True
87
+ uninstallMongoDb = True
88
+ uninstallSLS = True
89
+ else:
90
+ uninstallMongoDb = prompt(HTML(f'<Yellow>Uninstall MongoDb? </Yellow>'), validator=YesNoValidator()) in ["y", "yes"]
91
+ if uninstallMongoDb:
92
+ # If you are removing MongoDb then SLS needs to be uninstalled too
93
+ uninstallSLS = True
94
+ else:
95
+ uninstallSLS = prompt(HTML(f'<Yellow>Uninstall IBM Suite Licensing Service? </Yellow>'), validator=YesNoValidator()) in ["y", "yes"]
96
+
97
+ uninstallGrafana = prompt(HTML(f'<Yellow>Uninstall Grafana? </Yellow>'), validator=YesNoValidator()) in ["y", "yes"]
98
+ uninstallIBMCatalog = prompt(HTML(f'<Yellow>Uninstall IBM Catalog Source? </Yellow>'), validator=YesNoValidator()) in ["y", "yes"]
99
+ if uninstallIBMCatalog:
100
+ # If you choose to uninstall IBM Operator Catalog, everything from the catalog will be uninstalled
101
+ uninstallCommonServices = True
102
+ uninstallUDS = True
103
+ uninstallMongoDb = True
104
+ uninstallSLS = True
105
+ else:
106
+ uninstallCommonServices = prompt(HTML(f'<Yellow>Uninstall IBM Common Services? </Yellow>'), validator=YesNoValidator()) in ["y", "yes"]
107
+ uninstallUDS = prompt(HTML(f'<Yellow>Uninstall IBM User Data Services? </Yellow>'), validator=YesNoValidator()) in ["y", "yes"]
108
+
109
+ else:
110
+ # Non-interactive mode
111
+ if not verifyMasInstance(self.dynamicClient, instanceId):
112
+ print_formatted_text(HTML(f"<Red>Error: MAS Instance {instanceId} not found on this cluster</Red>"))
113
+ sys.exit(1)
114
+
115
+ # Default to Red Hat Cert-Manager, and check if IBM cert-manager is installed
116
+ certManagerProvider="redhat"
117
+ namespaceAPI = self.dynamicClient.resources.get(api_version="v1", kind="Namespace")
118
+ try:
119
+ # Check if 'ibm-common-services' namespace exist, this will throw NotFoundError exception when not found.
120
+ namespaceAPI.get(name="ibm-common-services")
121
+ podsAPI = self.dynamicClient.resources.get(api_version="v1", kind="Pod")
122
+ podsList = podsAPI.get(namespace="ibm-common-services")
123
+ for pod in podsList:
124
+ if pod is not None and pod.metadata.name.contains("cert-manager-cainjector"):
125
+ certManagerProvider = "ibm"
126
+ except NotFoundError:
127
+ print()
128
+ # ibm cert manager not found, proceed with default redhat.
129
+
130
+ self.printH1("Review Settings")
131
+ print_formatted_text(HTML(f"<LightSlateGrey>Instance ID ..................... {instanceId}</LightSlateGrey>"))
132
+ print_formatted_text(HTML(f"<LightSlateGrey>Uninstall Cert-Manager .......... {uninstallCertManager} ({certManagerProvider})</LightSlateGrey>"))
133
+ print_formatted_text(HTML(f"<LightSlateGrey>Uninstall Grafana ............... {uninstallGrafana}</LightSlateGrey>"))
134
+ print_formatted_text(HTML(f"<LightSlateGrey>Uninstall IBM Operator Catalog .. {uninstallIBMCatalog}</LightSlateGrey>"))
135
+ print_formatted_text(HTML(f"<LightSlateGrey>Uninstall IBM Common Services ... {uninstallCommonServices}</LightSlateGrey>"))
136
+ print_formatted_text(HTML(f"<LightSlateGrey>Uninstall UDS ................... {uninstallUDS}</LightSlateGrey>"))
137
+ print_formatted_text(HTML(f"<LightSlateGrey>Uninstall MongoDb ............... {uninstallMongoDb}</LightSlateGrey>"))
138
+ print_formatted_text(HTML(f"<LightSlateGrey>Uninstall SLS ................... {uninstallSLS}</LightSlateGrey>"))
139
+
140
+ if not noConfirm:
141
+ print()
142
+ continueWithUninstall = prompt(HTML(f'<Yellow>Proceed with these settings?</Yellow> '), validator=YesNoValidator(), validate_while_typing=False)
143
+
144
+ if noConfirm or continueWithUninstall in ["y", "yes"]:
145
+ self.printH1("Launch uninstall")
146
+ pipelinesNamespace = f"mas-{instanceId}-pipelines"
147
+
148
+ with Halo(text='Validating OpenShift Pipelines installation', spinner=self.spinner) as h:
149
+ installOpenShiftPipelines(self.dynamicClient)
150
+ h.stop_and_persist(symbol=self.successIcon, text=f"OpenShift Pipelines Operator is installed and ready to use")
151
+
152
+ with Halo(text=f'Preparing namespace ({pipelinesNamespace})', spinner=self.spinner) as h:
153
+ createNamespace(self.dynamicClient, pipelinesNamespace)
154
+ h.stop_and_persist(symbol=self.successIcon, text=f"Namespace is ready ({pipelinesNamespace})")
155
+
156
+ with Halo(text=f'Installing latest Tekton definitions (v{self.version})', spinner=self.spinner) as h:
157
+ updateTektonDefinitions(pipelinesNamespace, self.tektonDefsPath)
158
+ h.stop_and_persist(symbol=self.successIcon, text=f"Latest Tekton definitions are installed (v{self.version})")
159
+
160
+ with Halo(text='Submitting PipelineRun for {instanceId} uninstall', spinner=self.spinner) as h:
161
+ pipelineURL = launchUninstallPipeline(
162
+ dynClient = self.dynamicClient,
163
+ instanceId = instanceId,
164
+ certManagerProvider = "redhat",
165
+ uninstallCertManager = uninstallCertManager,
166
+ uninstallGrafana = uninstallGrafana,
167
+ uninstallCatalog = uninstallCommonServices,
168
+ uninstallCommonServices = uninstallCommonServices,
169
+ uninstallUDS = uninstallUDS,
170
+ uninstallMongoDb = uninstallMongoDb,
171
+ uninstallSLS = uninstallSLS
172
+ )
173
+ if pipelineURL is not None:
174
+ h.stop_and_persist(symbol=self.successIcon, text=f"PipelineRun for {instanceId} uninstall submitted")
175
+ print_formatted_text(HTML(f"\nView progress:\n <Cyan><u>{pipelineURL}</u></Cyan>\n"))
176
+ else:
177
+ h.stop_and_persist(symbol=self.failureIcon, text=f"Failed to submit PipelineRun for {instanceId} uninstall, see log file for details")
178
+ print()
179
+
180
+ if __name__ == '__main__':
181
+ parser = argparse.ArgumentParser(
182
+ prog='mas uninstall',
183
+ description="\n".join([
184
+ f"IBM Maximo Application Suite Admin CLI v{packageVersion}",
185
+ "Uninstall MAS by configuring and launching the MAS Uninstall Tekton Pipeline.\n",
186
+ "Interactive Mode:",
187
+ "Omitting the --instance-id option will trigger an interactive prompt"
188
+ ]),
189
+ epilog="Refer to the online documentation for more information: https://ibm-mas.github.io/cli/",
190
+ formatter_class=getHelpFormatter(),
191
+ add_help=False
192
+ )
193
+
194
+ masArgGroup = parser.add_argument_group('MAS Instance Selection')
195
+ masArgGroup.add_argument(
196
+ '--instance-id',
197
+ required=False,
198
+ help="The MAS instance ID to be uninstalled"
199
+ )
200
+
201
+ depsArgGroup = parser.add_argument_group('MAS Dependencies Selection')
202
+ depsArgGroup.add_argument(
203
+ '--uninstall-all-deps',
204
+ required=False,
205
+ action='store_true',
206
+ default=False,
207
+ help="Uninstall all MAS-related dependencies from the target cluster",
208
+ )
209
+
210
+ depsArgGroup.add_argument(
211
+ '--uninstall-cert-manager',
212
+ required=False,
213
+ action='store_true',
214
+ default=False,
215
+ help="Uninstall Certificate Manager from the target cluster",
216
+ )
217
+ depsArgGroup.add_argument(
218
+ '--uninstall-common-services',
219
+ required=False,
220
+ action='store_true',
221
+ default=False,
222
+ help="Uninstall IBM Common Services from the target cluster",
223
+ )
224
+ depsArgGroup.add_argument(
225
+ '--uninstall-grafana',
226
+ required=False,
227
+ action='store_true',
228
+ default=False,
229
+ help="Uninstall Grafana from the target cluster",
230
+ )
231
+ depsArgGroup.add_argument(
232
+ '--uninstall-ibm-catalog',
233
+ required=False,
234
+ action='store_true',
235
+ default=False,
236
+ help="Uninstall the IBM Maximo Operator Catalog Source (ibm-operator-catalog) from the target cluster",
237
+ )
238
+ depsArgGroup.add_argument(
239
+ '--uninstall-mongodb',
240
+ required=False,
241
+ action='store_true',
242
+ default=False,
243
+ help="Uninstall MongoDb from the target cluster",
244
+ )
245
+ depsArgGroup.add_argument(
246
+ '--uninstall-sls',
247
+ required=False,
248
+ action='store_true',
249
+ default=False,
250
+ help="Uninstall IBM Suite License Service from the target cluster",
251
+ )
252
+ depsArgGroup.add_argument(
253
+ '--uninstall-uds',
254
+ required=False,
255
+ action='store_true',
256
+ default=False,
257
+ help="Uninstall IBM User Data Services from the target cluster",
258
+ )
259
+
260
+ otherArgGroup = parser.add_argument_group('More')
261
+ otherArgGroup.add_argument(
262
+ '--no-confirm',
263
+ required=False,
264
+ action='store_true',
265
+ default=False,
266
+ help="Launch the upgrade without prompting for confirmation",
267
+ )
268
+ otherArgGroup.add_argument(
269
+ '-h', "--help",
270
+ action='help',
271
+ default=False,
272
+ help="Show this help message and exit",
273
+ )
274
+
275
+ args = parser.parse_args()
276
+
277
+ try:
278
+ app = App()
279
+ app.uninstall(args)
280
+ except KeyboardInterrupt as e:
281
+ pass
@@ -0,0 +1,156 @@
1
+ #!python
2
+ # *****************************************************************************
3
+ # Copyright (c) 2024 IBM Corporation and other Contributors.
4
+ #
5
+ # All rights reserved. This program and the accompanying materials
6
+ # are made available under the terms of the Eclipse Public License v1.0
7
+ # which accompanies this distribution, and is available at
8
+ # http://www.eclipse.org/legal/epl-v10.html
9
+ #
10
+ # *****************************************************************************
11
+
12
+ import argparse
13
+ import sys
14
+ import logging
15
+ import logging.handlers
16
+ from prompt_toolkit import prompt, print_formatted_text, HTML
17
+ from prompt_toolkit.completion import WordCompleter
18
+
19
+ from halo import Halo
20
+
21
+ from mas.cli import __version__ as packageVersion
22
+ from mas.cli.cli import InstanceIDValidator, YesNoValidator, BaseApp, getHelpFormatter
23
+ from mas.devops.ocp import createNamespace
24
+ from mas.devops.mas import listMasInstances, verifyMasInstance
25
+ from mas.devops.tekton import installOpenShiftPipelines, updateTektonDefinitions, launchUpgradePipeline
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ class App(BaseApp):
30
+ def upgrade(self, instanceId, skipPreCheck, noConfirm):
31
+ """
32
+ Upgrade MAS instance
33
+ """
34
+
35
+ if instanceId is None:
36
+ self.printH1("Set Target OpenShift Cluster")
37
+ # Connect to the target cluster
38
+ self.connect(noConfirm)
39
+ else:
40
+ logger.debug("MAS instance ID is set, so we assume already connected to the desired OCP")
41
+
42
+ if self.dynamicClient is None:
43
+ print_formatted_text(HTML("<Red>Error: The Kubernetes dynamic Client is not available. See log file for details</Red>"))
44
+ sys.exit(1)
45
+
46
+ if instanceId is None:
47
+ # Interactive mode
48
+ self.printH1("Instance Selection")
49
+ print_formatted_text(HTML("<LightSlateGrey>Select a MAS instance to upgrade from the list below:</LightSlateGrey>"))
50
+ suites = listMasInstances(self.dynamicClient)
51
+ suiteOptions = []
52
+
53
+ if len(suites) == 0:
54
+ print_formatted_text(HTML(f"<Red>Error: No MAS instances detected on this cluster</Red>"))
55
+ sys.exit(1)
56
+
57
+ for suite in suites:
58
+ print_formatted_text(HTML(f"- <u>{suite['metadata']['name']}</u> v{suite['status']['versions']['reconciled']}"))
59
+ suiteOptions.append(suite['metadata']['name'])
60
+
61
+ suiteCompleter = WordCompleter(suiteOptions)
62
+ print()
63
+ instanceId = prompt(HTML(f'<Yellow>Enter MAS instance ID: </Yellow>'), completer=suiteCompleter, validator=InstanceIDValidator(), validate_while_typing=False)
64
+ else:
65
+ # Non-interactive mode
66
+ if not verifyMasInstance(self.dynamicClient, instanceId):
67
+ print_formatted_text(HTML(f"<Red>Error: MAS instance {instanceId} not found on this cluster</Red>"))
68
+ sys.exit(1)
69
+
70
+ self.printH1("Review Settings")
71
+ print_formatted_text(HTML(f"<LightSlateGrey>Instance ID ..................... {instanceId}</LightSlateGrey>"))
72
+ print_formatted_text(HTML(f"<LightSlateGrey>Skip Pre-Upgrade Checks ......... {skipPreCheck}</LightSlateGrey>"))
73
+
74
+ if not noConfirm:
75
+ print()
76
+ continueWithUpgrade = prompt(HTML(f'<Yellow>Proceed with these settings?</Yellow> '), validator=YesNoValidator(), validate_while_typing=False)
77
+
78
+ if noConfirm or continueWithUpgrade in ["y", "yes"]:
79
+ self.printH1("Launch Upgrade")
80
+ pipelinesNamespace = f"mas-{instanceId}-pipelines"
81
+
82
+ with Halo(text='Validating OpenShift Pipelines installation', spinner=self.spinner) as h:
83
+ installOpenShiftPipelines(self.dynamicClient)
84
+ h.stop_and_persist(symbol=self.successIcon, text=f"OpenShift Pipelines Operator is installed and ready to use")
85
+
86
+ with Halo(text=f'Preparing namespace ({pipelinesNamespace})', spinner=self.spinner) as h:
87
+ createNamespace(self.dynamicClient, pipelinesNamespace)
88
+ h.stop_and_persist(symbol=self.successIcon, text=f"Namespace is ready ({pipelinesNamespace})")
89
+
90
+ with Halo(text=f'Installing latest Tekton definitions (v{self.version})', spinner=self.spinner) as h:
91
+ updateTektonDefinitions(pipelinesNamespace, self.tektonDefsPath)
92
+ h.stop_and_persist(symbol=self.successIcon, text=f"Latest Tekton definitions are installed (v{self.version})")
93
+
94
+ with Halo(text='Submitting PipelineRun for {instanceId} upgrade', spinner=self.spinner) as h:
95
+ pipelineURL = launchUpgradePipeline(self.dynamicClient, instanceId)
96
+ if pipelineURL is not None:
97
+ h.stop_and_persist(symbol=self.successIcon, text=f"PipelineRun for {instanceId} upgrade submitted")
98
+ print_formatted_text(HTML(f"\nView progress:\n <Cyan><u>{pipelineURL}</u></Cyan>\n"))
99
+ else:
100
+ h.stop_and_persist(symbol=self.failureIcon, text=f"Failed to submit PipelineRun for {instanceId} upgrade, see log file for details")
101
+ print()
102
+
103
+ if __name__ == '__main__':
104
+ parser = argparse.ArgumentParser(
105
+ prog='mas upgrade',
106
+ description="\n".join([
107
+ f"IBM Maximo Application Suite Admin CLI v{packageVersion}",
108
+ "Upgrade MAS by configuring and launching the MAS Upgrade Tekton Pipeline.\n",
109
+ "Interactive Mode:",
110
+ "Omitting the --instance-id option will trigger an interactive prompt"
111
+ ]),
112
+ epilog="Refer to the online documentation for more information: https://ibm-mas.github.io/cli/",
113
+ formatter_class=getHelpFormatter(),
114
+ add_help=False
115
+ )
116
+
117
+ masArgGroup = parser.add_argument_group('MAS Instance Selection')
118
+ masArgGroup.add_argument(
119
+ '--instance-id',
120
+ required=False,
121
+ help="The MAS instance ID to be upgraded"
122
+ )
123
+
124
+ otherArgGroup = parser.add_argument_group('More')
125
+ otherArgGroup.add_argument(
126
+ '--skip-pre-check',
127
+ required=False,
128
+ action='store_true',
129
+ default=False,
130
+ help="Disable the 'pre-upgrade-check' and 'post-upgrade-verify' tasks in the upgrade pipeline"
131
+ )
132
+ otherArgGroup.add_argument(
133
+ '--no-confirm',
134
+ required=False,
135
+ action='store_true',
136
+ default=False,
137
+ help="Launch the upgrade without prompting for confirmation",
138
+ )
139
+ otherArgGroup.add_argument(
140
+ '-h', "--help",
141
+ action='help',
142
+ default=False,
143
+ help="Show this help message and exit",
144
+ )
145
+
146
+ args = parser.parse_args()
147
+
148
+ try:
149
+ app = App()
150
+ app.upgrade(
151
+ args.instance_id,
152
+ args.skip_pre_check,
153
+ args.no_confirm
154
+ )
155
+ except KeyboardInterrupt as e:
156
+ pass
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.1
2
+ Name: mas-cli
3
+ Version: 9.5.0
4
+ Summary: Python Admin CLI for Maximo Application Suite
5
+ Home-page: https://github.com/ibm-mas/cli
6
+ Author: David Parker
7
+ Author-email: parkerda@uk.ibm.com
8
+ License: Eclipse Public License - v1.0
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Topic :: Communications
18
+ Classifier: Topic :: Internet
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Dist: mas-devops
21
+ Requires-Dist: halo
22
+ Requires-Dist: prompt-toolkit
23
+ Requires-Dist: openshift
24
+ Requires-Dist: kubernetes
25
+ Provides-Extra: dev
26
+ Requires-Dist: build ; extra == 'dev'
27
+ Requires-Dist: flake8 ; extra == 'dev'
28
+ Requires-Dist: pytest ; extra == 'dev'
29
+ Requires-Dist: pyinstaller ; extra == 'dev'
30
+
31
+ mas.devops
32
+ ----------
33
+
34
+ Example
35
+ =======
36
+
37
+ .. code:: python
38
+
39
+ from openshift import dynamic
40
+ from kubernetes import config
41
+ from kubernetes.client import api_client
42
+
43
+ from mas.devops.ocp import createNamespace
44
+ from mas.devops.tekton import installOpenShiftPipelines, updateTektonDefinitions, launchUpgradePipeline
45
+
46
+ instanceId = "mymas"
47
+ pipelinesNamespace = f"mas-{instanceId}-pipelines"
48
+
49
+ # Create an OpenShift client
50
+ dynClient = dynamic.DynamicClient(
51
+ api_client.ApiClient(configuration=config.load_kube_config())
52
+ )
53
+
54
+ # Install OpenShift Pipelines Operator
55
+ installOpenShiftPipelines(dynamicClient)
56
+
57
+ # Create the pipelines namespace and install the MAS tekton definitions
58
+ createNamespace(dynamicClient, pipelinesNamespace)
59
+ updateTektonDefinitions(pipelinesNamespace)
60
+
61
+ # Launch the upgrade pipeline and print the URL to view the pipeline run
62
+ pipelineURL = launchUpgradePipeline(self.dynamicClient, instanceId)
63
+ print(pipelineURL)
@@ -0,0 +1,8 @@
1
+ mas/cli/__init__.py,sha256=gtCpZLbQvVbt2Kyub5QSZEF-tEs4fZvBlYW9rtVzHAg,490
2
+ mas/cli/cli.py,sha256=6FoiYRG297LN2P02p5f28qjTQ__4x_WV0f8yBjZEX2I,8649
3
+ mas_cli-9.5.0.data/scripts/mas-uninstall,sha256=aCySCt7zJvoB2HMPR0Ftt9O0hKT5a1-GTFbMw2ZJ33k,13176
4
+ mas_cli-9.5.0.data/scripts/mas-upgrade,sha256=5zMbjueTAzwmaSxa1F3oiaQvOeiPobsOtwUxXqrg36Y,6834
5
+ mas_cli-9.5.0.dist-info/METADATA,sha256=5mVGmPn5jL1w4SdYqqpO_e2-L-Mn5CsORfiTTPpniWc,2051
6
+ mas_cli-9.5.0.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
7
+ mas_cli-9.5.0.dist-info/top_level.txt,sha256=_Hlsp7pvMvyV14LFg-vk1hULq30j61EILnnxMFIhhc8,4
8
+ mas_cli-9.5.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (70.1.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ mas