Skip to content

Commit a73bef8

Browse files
authored
[minor] Improved SLS configuration flow (#1438)
Co-authored-by: Rawa Resul <rawa.resul@ibm.com>
1 parent 61de895 commit a73bef8

9 files changed

Lines changed: 123 additions & 32 deletions

File tree

docs/reference/topology.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
- A dedicated manage database will be named `mas-{instanceId}-{workspaceId}-manage`
1010
- Multiple `Kafka` clusters will be created using either Strimzi or Red Hat AMQ Streams operators, one for each MAS instance.
1111
- The instance will be named `mas-{instanceId}-system`
12-
- A single SLS `LicenseService` instance will be created, all MAS instances in the cluster will share the same pool of AppPoints from a single license file loaded into SLS. Each MAS instance is registered to SLS independently, uniquely identifying it to SLS.
12+
- A single SLS `LicenseService` instance will be created, all MAS instances in the cluster will share the same pool of AppPoints from a single license file loaded into SLS. Each MAS instance is registered to SLS independently, uniquely identifying it to SLS. Alternatively, a dedicated SLS per MAS instance can be installed on the cluster.
1313
- A single UDS `AnalyticsProxy` instance will be created, all MAS instaled in the cluster will be configured to report to this instance using a shared API key.
1414

1515
## High-Level View

python/src/mas/cli/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,10 @@ def __init__(self):
132132
# Initialize the dictionary that will hold the parameters we pass to a PipelineRun
133133
self.params = dict()
134134

135-
# These dicts will hold the additional-configs, pod-templates and manual certificates secrets
135+
# These dicts will hold the additional-configs, pod-templates, sls license file and manual certificates secrets
136136
self.additionalConfigsSecret = None
137137
self.podTemplatesSecret = None
138+
self.slsLicenseFileSecret = None
138139
self.certsSecret = None
139140

140141
self._isSNO = None

python/src/mas/cli/install/app.py

Lines changed: 81 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545

4646
from mas.devops.ocp import createNamespace, getStorageClasses
4747
from mas.devops.mas import getCurrentCatalog, getDefaultStorageClasses
48+
from mas.devops.sls import findSLSByNamespace
4849
from mas.devops.data import getCatalog
4950
from mas.devops.tekton import (
5051
installOpenShiftPipelines,
@@ -158,9 +159,6 @@ def formatCatalog(self, name: str) -> str:
158159
year = date[:2]
159160
return f" - {monthName} 20{year} Update\n <Orange><u>https://ibm-mas.github.io/cli/catalogs/{name}</u></Orange>"
160161

161-
def formatRelease(self, release: str) -> str:
162-
return f"{release} ... {self.catalogReleases[release]['core']}"
163-
164162
@logMethodCall
165163
def processCatalogChoice(self) -> list:
166164
self.catalogDigest = self.chosenCatalog["catalog_digest"]
@@ -184,25 +182,31 @@ def processCatalogChoice(self) -> list:
184182
"Manage": "mas_manage_version",
185183
}
186184

187-
self.catalogReleases = []
185+
self.catalogReleases = {}
188186
self.catalogTable = []
189187

190188
# Dynamically fetch the channels from the chosen catalog
191189
# based on mas core
192190
for channel in self.chosenCatalog["mas_core_version"]:
193-
self.catalogReleases.append(channel)
191+
# {"9.1-feature": "9.1.x-feature"}
192+
self.catalogReleases.update({channel.replace('.x', ''): channel})
194193

195194
# Generate catalogTable
196195
for application, key in applications.items():
197-
self.catalogTable.append({"": application} | self.chosenCatalog[key])
196+
# Add 9.1-feature channel based off 9.0 to those apps that have not onboarded yet
197+
tempChosenCatalog = self.chosenCatalog[key].copy()
198+
if '9.1.x-feature' not in tempChosenCatalog:
199+
tempChosenCatalog.update({"9.1.x-feature": tempChosenCatalog["9.0.x"]})
200+
201+
self.catalogTable.append({"": application} | {key.replace(".x", ""): value for key, value in sorted(tempChosenCatalog.items(), reverse=True)})
198202

199203
if self.architecture == "s390x":
200204
summary = [
201205
"",
202206
"<u>Catalog Details</u>",
203207
f"Catalog Image: icr.io/cpopen/ibm-maximo-operator-catalog:{self.getParam('mas_catalog_version')}",
204208
f"Catalog Digest: {self.catalogDigest}",
205-
f"MAS Releases: {', '.join(self.catalogReleases)}",
209+
f"MAS Releases: {', '.join(sorted(self.catalogReleases, reverse=True))}",
206210
f"MongoDb: {self.catalogMongoDbVersion}",
207211
]
208212
else:
@@ -211,7 +215,7 @@ def processCatalogChoice(self) -> list:
211215
"<u>Catalog Details</u>",
212216
f"Catalog Image: icr.io/cpopen/ibm-maximo-operator-catalog:{self.getParam('mas_catalog_version')}",
213217
f"Catalog Digest: {self.catalogDigest}",
214-
f"MAS Releases: {', '.join(self.catalogReleases)}",
218+
f"MAS Releases: {', '.join(sorted(self.catalogReleases, reverse=True))}",
215219
f"Cloud Pak for Data: {self.catalogCp4dVersion}",
216220
f"MongoDb: {self.catalogMongoDbVersion}",
217221
]
@@ -254,28 +258,64 @@ def configCatalog(self):
254258
self.printDescription(catalogSummary)
255259
self.printDescription([
256260
"",
257-
"Multiple releases of Maximo Application Suite are available, each is supported under IBM's standard 3+1+3 support model.",
258-
"Choose the release of IBM Maximo Application Suite that you want to use for this installation from the table below:",
261+
"Two types of release are available:",
262+
" - GA releases of Maximo Application Suite are supported under IBM's standard 3+1+3 support lifecycle policy.",
263+
" - 'Feature' releases allow early access to new features for evaluation in non-production environments and are only supported through to the next GA release.",
259264
""
260265
])
261266

262267
print(tabulate(self.catalogTable, headers="keys", tablefmt="simple_grid"))
263268

264-
releaseCompleter = WordCompleter(self.catalogReleases)
269+
releaseCompleter = WordCompleter(sorted(self.catalogReleases, reverse=True))
265270
releaseSelection = self.promptForString("Select release", completer=releaseCompleter)
266271

267-
self.setParam("mas_channel", releaseSelection)
272+
self.setParam("mas_channel", self.catalogReleases[releaseSelection])
268273

269274
@logMethodCall
270275
def configSLS(self) -> None:
271-
self.printH1("Configure Product License")
276+
self.printH1("Configure AppPoint Licensing")
277+
self.printDescription(
278+
[
279+
"By default the MAS instance will be configured to use a cluster-shared License, this provides a shared pool of AppPoints available to all MAS instances on the cluster.",
280+
"",
281+
]
282+
)
283+
284+
self.slsMode = 1
285+
self.slsLicenseFileLocal = None
286+
287+
if self.showAdvancedOptions:
288+
self.printDescription(
289+
[
290+
"Alternatively you may choose to install using a dedicated license only available to this MAS instance.",
291+
" 1. Install MAS with Cluster-Shared License (AppPoints)",
292+
" 2. Install MAS with Dedicated License (AppPoints)",
293+
]
294+
)
295+
self.slsMode = self.promptForInt("SLS Mode", default=1)
296+
297+
if self.slsMode not in [1, 2]:
298+
self.fatalError(f"Invalid selection: {self.slsMode}")
299+
300+
if not (self.slsMode == 2 and not self.getParam("sls_namespace")):
301+
sls_namespace = "ibm-sls" if self.slsMode == 1 else self.getParam("sls_namespace")
302+
if findSLSByNamespace(sls_namespace, dynClient=self.dynamicClient):
303+
print_formatted_text(HTML(f"<MediumSeaGreen>SLS auto-detected: {sls_namespace}</MediumSeaGreen>"))
304+
print()
305+
if not self.yesOrNo("Upload/Replace the license file"):
306+
self.setParam("sls_action", "gencfg")
307+
return
308+
272309
self.slsLicenseFileLocal = self.promptForFile("License file", mustExist=True, envVar="SLS_LICENSE_FILE_LOCAL")
310+
self.setParam("sls_action", "install")
311+
312+
@logMethodCall
313+
def configDRO(self) -> None:
273314
self.promptForString("Contact e-mail address", "uds_contact_email")
274315
self.promptForString("Contact first name", "uds_contact_firstname")
275316
self.promptForString("Contact last name", "uds_contact_lastname")
276317

277318
if self.showAdvancedOptions:
278-
self.promptForString("IBM Suite License Services (SLS) Namespace", "sls_namespace", default="ibm-sls")
279319
self.promptForString("IBM Data Reporter Operator (DRO) Namespace", "dro_namespace", default="redhat-marketplace")
280320

281321
@logMethodCall
@@ -396,6 +436,9 @@ def configMAS(self):
396436
])
397437
self.promptForString("Workspace name", "mas_workspace_name", validator=WorkspaceNameFormatValidator())
398438

439+
if self.slsMode == 2 and not self.getParam("sls_namespace"):
440+
self.setParam("sls_namespace", f"mas-{self.getParam('mas_instance_id')}-sls")
441+
399442
self.configOperationMode()
400443
self.configCATrust()
401444
self.configDNSAndCerts()
@@ -747,6 +790,7 @@ def interactiveMode(self, simplified: bool, advanced: bool) -> None:
747790

748791
# Licensing (SLS and DRO)
749792
self.configSLS()
793+
self.configDRO()
750794
self.configICRCredentials()
751795

752796
# MAS Core
@@ -795,6 +839,7 @@ def nonInteractiveMode(self) -> None:
795839
self.deployCP4D = False
796840
self.db2SetAffinity = False
797841
self.db2SetTolerations = False
842+
self.slsLicenseFileLocal = None
798843

799844
self.approvals = {
800845
"approval_core": {"id": "suite-verify"}, # After Core Platform verification has completed
@@ -903,6 +948,14 @@ def nonInteractiveMode(self) -> None:
903948
self.setParam(key, value)
904949
if value in ["jms", "snojms"]:
905950
self.setParam("mas_app_settings_persistent_volumes_flag", "true")
951+
# SLS
952+
elif key == "license_file":
953+
if value is not None and value != "":
954+
self.slsLicenseFileLocal = value
955+
self.setParam("sls_action", "install")
956+
elif key == "dedicated_sls":
957+
if value:
958+
self.setParam("sls_namespace", f"mas-{self.args.mas_instance_id}-sls")
906959

907960
# These settings are used by the CLI rather than passed to the PipelineRun
908961
elif key == "storage_accessmode":
@@ -913,10 +966,6 @@ def nonInteractiveMode(self) -> None:
913966
if value is None:
914967
self.fatalError(f"{key} must be set")
915968
self.pipelineStorageClass = value
916-
elif key == "license_file":
917-
if value is None:
918-
self.fatalError(f"{key} must be set")
919-
self.slsLicenseFileLocal = value
920969

921970
elif key.startswith("approval_"):
922971
if key not in self.approvals:
@@ -954,6 +1003,13 @@ def nonInteractiveMode(self) -> None:
9541003
# Load the catalog information
9551004
self.chosenCatalog = getCatalog(self.getParam("mas_catalog_version"))
9561005

1006+
# License file is only optional for existing SLS instance
1007+
if self.slsLicenseFileLocal is None:
1008+
if findSLSByNamespace(self.getParam("sls_namespace"), dynClient=self.dynamicClient):
1009+
self.setParam("sls_action", "gencfg")
1010+
else:
1011+
self.fatalError("--license-file must be set for new SLS install")
1012+
9571013
# Once we've processed the inputs, we should validate the catalog source & prompt to accept the license terms
9581014
if not self.devMode:
9591015
self.validateCatalogSource()
@@ -977,6 +1033,10 @@ def install(self, argv):
9771033
self.devMode = args.dev_mode
9781034
self.skipGrafanaInstall = args.skip_grafana_install
9791035

1036+
# Set image_pull_policy of the CLI in interactive mode
1037+
if args.image_pull_policy and args.image_pull_policy != "":
1038+
self.setParam("image_pull_policy", args.image_pull_policy)
1039+
9801040
self.approvals = {}
9811041

9821042
# Store all args
@@ -1024,13 +1084,10 @@ def install(self, argv):
10241084
if self.deployCP4D:
10251085
self.configCP4D()
10261086

1027-
# The entitlement file for SLS is mounted as a secret in /workspace/entitlement
1028-
entitlementFileBaseName = path.basename(self.slsLicenseFileLocal)
1029-
self.setParam("sls_entitlement_file", f"/workspace/entitlement/{entitlementFileBaseName}")
1030-
1031-
# Set up the secrets for additional configs, podtemplates and manual certificates
1087+
# Set up the secrets for additional configs, podtemplates, sls license file and manual certificates
10321088
self.additionalConfigs()
10331089
self.podTemplates()
1090+
self.slsLicenseFile()
10341091
self.manualCertificates()
10351092

10361093
# Show a summary of the installation configuration
@@ -1082,7 +1139,7 @@ def install(self, argv):
10821139
prepareInstallSecrets(
10831140
dynClient=self.dynamicClient,
10841141
instanceId=self.getParam("mas_instance_id"),
1085-
slsLicenseFile=self.slsLicenseFileLocal,
1142+
slsLicenseFile=self.slsLicenseFileSecret,
10861143
additionalConfigs=self.additionalConfigsSecret,
10871144
podTemplates=self.podTemplatesSecret,
10881145
certs=self.certsSecret

python/src/mas/cli/install/argBuilder.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,15 @@ def buildCommand(self) -> str:
122122

123123
# IBM Suite License Service
124124
# -----------------------------------------------------------------------------
125-
command += f" --license-file \"{self.slsLicenseFileLocal}\"{newline}"
126-
if self.getParam("sls_namespace") != "ibm-sls":
127-
command += f" --sls-namespace \"{self.getParam('sls_namespace')}\"{newline}"
125+
if self.getParam("sls_namespace") and self.getParam("sls_namespace") != "ibm-sls":
126+
if self.getParam("mas_instance_id") and self.getParam("sls_namespace") == f"mas-{self.getParam('mas_instance_id')}-sls":
127+
command += " --dedicated-sls"
128+
else:
129+
command += f" --sls-namespace \"{self.getParam('sls_namespace')}\""
130+
if self.slsLicenseFileLocal:
131+
command += f" --license-file \"{self.slsLicenseFileLocal}\""
132+
if self.getParam("sls_namespace") and self.getParam("sls_namespace") != "ibm-sls" or self.slsLicenseFileLocal:
133+
command += newline
128134

129135
# IBM Data Reporting Operator (DRO)
130136
# -----------------------------------------------------------------------------

python/src/mas/cli/install/argParser.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,12 @@ def isValidFile(parser, arg) -> str:
263263
help="Customize the SLS install namespace",
264264
default="ibm-sls"
265265
)
266+
slsArgGroup.add_argument(
267+
"--dedicated-sls",
268+
action="store_true",
269+
default=False,
270+
help="Set the SLS namespace to mas-<instanceid>-sls"
271+
)
266272

267273
# IBM Data Reporting Operator (DRO)
268274
# -----------------------------------------------------------------------------

python/src/mas/cli/install/settings/additionalConfigs.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,19 @@ def manualCertificates(self) -> None:
182182

183183
self.certsSecret = certsSecret
184184

185+
def slsLicenseFile(self) -> None:
186+
if self.slsLicenseFileLocal:
187+
slsLicenseFileSecret = {
188+
"apiVersion": "v1",
189+
"kind": "Secret",
190+
"type": "Opaque",
191+
"metadata": {
192+
"name": "pipeline-sls-entitlement"
193+
}
194+
}
195+
self.setParam("sls_entitlement_file", f"/workspace/entitlement/{path.basename(self.slsLicenseFileLocal)}")
196+
self.slsLicenseFileSecret = self.addFilesToSecret(slsLicenseFileSecret, self.slsLicenseFileLocal, '')
197+
185198
def addFilesToSecret(self, secretDict: dict, configPath: str, extension: str, keyPrefix: str = '') -> dict:
186199
"""
187200
Add file (or files) to pipeline-additional-configs

python/src/mas/cli/install/summarizer.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,9 +270,12 @@ def droSummary(self) -> None:
270270

271271
def slsSummary(self) -> None:
272272
self.printH2("IBM Suite License Service")
273-
self.printSummary("License File", self.slsLicenseFileLocal)
274-
self.printParamSummary("IBM Open Registry", "sls_icr_cpopen")
275273
self.printParamSummary("Namespace", "sls_namespace")
274+
if self.getParam("sls_action") == "install":
275+
self.printSummary("Subscription Channel", "3.x")
276+
self.printParamSummary("IBM Open Registry", "sls_icr_cpopen")
277+
if self.slsLicenseFileLocal:
278+
self.printSummary("License File", self.slsLicenseFileLocal)
276279

277280
def cosSummary(self) -> None:
278281
self.printH2("Cloud Object Storage")

tekton/src/params/install.yml.j2

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
default: ""
2828
- name: sls_entitlement_file
2929
type: string
30-
default: "/workspace/entitlement/entitlement.lic"
30+
default: ""
3131
- name: sls_mongodb_cfg_file
3232
type: string
3333
# The default value works for the default in-cluster install, it will need
@@ -42,6 +42,9 @@
4242
- name: sls_icr_cpopen
4343
type: string
4444
default: ""
45+
- name: sls_action
46+
type: string
47+
default: ""
4548

4649
# Dependencies - MongoDb
4750
# -----------------------------------------------------------------------------

tekton/src/pipelines/taskdefs/dependencies/sls.yml.j2

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
value: $(params.sls_channel)
2424
- name: sls_icr_cpopen
2525
value: $(params.sls_icr_cpopen)
26+
- name: sls_action
27+
value: $(params.sls_action)
2628

2729
# New way of bootstrapping license file
2830
- name: sls_entitlement_file

0 commit comments

Comments
 (0)