Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[minor] Add support for various SLS configurations #1438

Draft
wants to merge 55 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
b2b466a
[patch] Add custom python-devops
Jan 11, 2025
9c13c79
[patch] Add SLS options
Jan 12, 2025
98488e9
[patch] Add further improvements
Jan 12, 2025
769250b
[patch] Fix validator
Jan 12, 2025
0d18cf3
[patch] Add NewNamespaceValidator
Jan 12, 2025
a51ceae
[patch] Update custom python-devops
Jan 12, 2025
16906ac
[patch] Fix invalid var referencing
Jan 12, 2025
55a44dc
[patch] SLS flow improvements
Jan 12, 2025
afb5c76
[patch] Update custom ansible-devops and python-devops
Jan 12, 2025
1e4dc99
[patch] Update SLS tekton definitions
Jan 12, 2025
52a6a4d
[patch] Add sls manual cert
Jan 12, 2025
0109b26
[patch] Fix validator
Jan 12, 2025
091e5c7
[patch] Fix bug in NewNamespaceValidator
Jan 12, 2025
e67632a
[patch] Add SLSInstanceSelectionValidator
Jan 13, 2025
4b174fb
[patch] Update SLS description
Jan 13, 2025
90981d4
[patch] Fix sls cert flow
Jan 13, 2025
285125a
[patch] Refactor sls flow
Jan 13, 2025
5741879
[patch] Update python-devops
Jan 13, 2025
f0f534f
[patch] Update ansible-devops
Jan 13, 2025
b21877d
[patch] Improve SLS flow
Jan 13, 2025
2145680
[patch] Set ImagePullPolicy to Always
Jan 13, 2025
98fad8b
[patch] Add image_pull_policy debug
Jan 13, 2025
96531e3
[patch] Fix imagePullPolicy
Jan 13, 2025
6693934
[patch] Check if args.image_pull_policy is not empty
Jan 13, 2025
ddfb73c
[patch] Update custom ansible-devops
Jan 14, 2025
77ec4cc
[patch] Fix SLS simple route message
Jan 14, 2025
3c610c6
[patch] Add SLS connection verification in external mode
Jan 14, 2025
dc7a805
[patch] Update custom python-devops
Jan 14, 2025
69979eb
[patch] Improve SLS simple flow
Jan 14, 2025
859046a
[patch] Make SLS License File optional for existing instance
Jan 14, 2025
53f90a2
[patch] Update custom python-devops
Jan 14, 2025
19bfd06
[patch] Update custom python-devops
Jan 14, 2025
8e0001d
[patch] Add slsLicenseFile function
Jan 14, 2025
d84b3f4
[patch] Update custom python-devops
Jan 14, 2025
a56a37f
[patch] Fix missing param in addFilesToSecret
Jan 14, 2025
4324eb2
[patch] Fix self.slsLicenseFileSecret
Jan 14, 2025
8998dac
[patch] Update custom ansible-devops
Jan 14, 2025
f493d46
Merge branch 'master' into sls-rr
rawa-resul Jan 14, 2025
38a9e08
[patch] Fix default sls namespace
Jan 15, 2025
ff308fd
[patch] Improve SLS validators
Jan 15, 2025
f601ee6
[patch] Fix message format
Jan 15, 2025
d0aaf6b
[patch] Fix msg
Jan 15, 2025
4a4c6e5
[patch] Improve sls simple route
Jan 15, 2025
38e6c82
[patch] Update SLS advanced flow
Jan 15, 2025
fefff8f
[patch] Update custom ansible-devops
Jan 15, 2025
5bcb840
[patch] Add debug in sls role
Jan 15, 2025
13344e7
[patch] Update custom ansible-devops
Jan 15, 2025
d627e07
[patch] Add encoding param to addFilesToSecret
Jan 15, 2025
ca722e2
[patch] Remove b64decode from mounted secret
Jan 16, 2025
eef95bb
[patch] Remove b64 env var
Jan 16, 2025
de8cecf
[patch] Add non-interactive support for new SLS flow
Jan 16, 2025
1ac32d9
[patch] Update custom python-devops
Jan 16, 2025
5cf1d3c
[patch] Fix arg name
Jan 16, 2025
f9f853b
[patch] Fix arg parser exception
Jan 16, 2025
23f048d
[patch] Fix argChecker
Jan 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added image/cli/install/ibm-mas_devops.tar.gz
Binary file not shown.
Binary file added image/cli/install/mas_devops.tar.gz
Binary file not shown.
3 changes: 2 additions & 1 deletion python/src/mas/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,10 @@ def __init__(self):
# Initialize the dictionary that will hold the parameters we pass to a PipelineRun
self.params = dict()

# These dicts will hold the additional-configs, pod-templates and manual certificates secrets
# These dicts will hold the additional-configs, pod-templates, sls license file and manual certificates secrets
self.additionalConfigsSecret = None
self.podTemplatesSecret = None
self.slsLicenseFileSecret = None
self.certsSecret = None

self._isSNO = None
Expand Down
138 changes: 125 additions & 13 deletions python/src/mas/cli/install/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from ..gencfg import ConfigGeneratorMixin
from .argBuilder import installArgBuilderMixin
from .argParser import installArgParser
from .argChecker import verifyArgs
from .settings import InstallSettingsMixin
from .summarizer import InstallSummarizerMixin
from .params import requiredParams, optionalParams
Expand All @@ -40,11 +41,15 @@
WorkspaceNameFormatValidator,
TimeoutFormatValidator,
StorageClassValidator,
OptimizerInstallPlanValidator
OptimizerInstallPlanValidator,
SLSConfigValidator,
SLSInstanceSelectionValidator,
NewNamespaceValidator
)

from mas.devops.ocp import createNamespace, getStorageClasses
from mas.devops.mas import getCurrentCatalog, getDefaultStorageClasses
from mas.devops.sls import listSLSInstances, verifySLSConnection, findSLSByNamespace
from mas.devops.data import getCatalog
from mas.devops.tekton import (
installOpenShiftPipelines,
Expand Down Expand Up @@ -249,16 +254,102 @@ def configCatalog(self):

self.setParam("mas_channel", releaseSelection)

@logMethodCall
def validateExternalSLSConnection(self) -> None:
self.verifySLSConnection = verifySLSConnection(self.getParam("sls_url"), self.slsCertsDirLocal + 'ca.crt')

if not self.noConfirm:
if not self.verifySLSConnection:
if not self.yesOrNo("Could not verify SLS connection, proceed anyway"):
exit(1)

@logMethodCall
def configSLS(self) -> None:
self.printH1("Configure Product License")
self.slsLicenseFileLocal = self.promptForFile("License file", mustExist=True, envVar="SLS_LICENSE_FILE_LOCAL")

self.slsLicenseFileLocal = None
self.slsCertsDirLocal = None
existingSLSInstances = listSLSInstances(self.dynamicClient)
numSLSInstances = len(existingSLSInstances)
description = [
"IBM Suite License Service (SLS) is the licensing enforcement for Maximo Application Suite.",
""
]

if self.showAdvancedOptions:
self.slsConfigOptions = []
self.slsInstanceOptions = []

description.insert(1, "Choose how to configure SLS:")
description.insert(2, " - New: Deploy a new instance on the cluster.")
description.insert(3, " - External: Point to an external instance outside of the cluster.")

self.slsConfigOptions.append("New")
self.slsConfigOptions.append("External")

if numSLSInstances > 0:
self.slsConfigOptions.insert(1, "Existing")
description.insert(3, " - Existing: Select an existing instance on the cluster. This is useful for sharing SLS with multiple MAS instances.")

slsConfigCompleter = WordCompleter(self.slsConfigOptions)

self.printDescription(description)
slsConfigSelection = self.promptForString("Select SLS config option", completer=slsConfigCompleter, validator=SLSConfigValidator())

if slsConfigSelection == "New":
self.setParam("sls_action", "install")
defaultVal = "ibm-sls"
if numSLSInstances > 0:
defaultVal = ""
self.promptForString("SLS namespace", "sls_namespace", default=defaultVal, validator=NewNamespaceValidator())
self.slsLicenseFileLocal = self.promptForFile("License file", mustExist=True, envVar="SLS_LICENSE_FILE_LOCAL")

if slsConfigSelection == "Existing":
self.setParam("sls_action", "gencfg")
print_formatted_text(HTML("<LightSlateGrey>Select an existing SLS instance from the list below:</LightSlateGrey>"))
for slsInstance in existingSLSInstances:
print_formatted_text(HTML(f"- <u>{slsInstance['metadata']['namespace']}</u> | {slsInstance['metadata']['name']} | v{slsInstance['status']['versions']['reconciled']}"))
self.slsInstanceOptions.append(slsInstance['metadata']['namespace'])
slsInstanceCompleter = WordCompleter(self.slsInstanceOptions)
print()
self.promptForString("Select SLS namespace", "sls_namespace", completer=slsInstanceCompleter, validator=SLSInstanceSelectionValidator())
if self.yesOrNo("Upload/Replace the license file"):
self.slsLicenseFileLocal = self.promptForFile("License file", mustExist=True, envVar="SLS_LICENSE_FILE_LOCAL")
self.setParam("sls_action", "install")

if slsConfigSelection == "External":
self.setParam("sls_action", "gencfg")
self.promptForString("SLS url", "sls_url")
self.promptForString("SLS registrationKey", "sls_registration_key")
self.slsCertsDirLocal = self.promptForDir("Enter the path containing the SLS certificate(s)", mustExist=True)
self.validateExternalSLSConnection()

else:
self.setParam("sls_action", "install")
sls_default_namespace = "ibm-sls"
self.setParam("sls_namespace", sls_default_namespace)

if numSLSInstances == 0:
description.insert(1, f"A new instance of SLS will be deployed on the cluster in the namespace '{sls_default_namespace}'.")
self.printDescription(description)
self.slsLicenseFileLocal = self.promptForFile("License file", mustExist=True, envVar="SLS_LICENSE_FILE_LOCAL")

if numSLSInstances > 0:
self.printDescription(description)
if findSLSByNamespace(sls_default_namespace, instances=existingSLSInstances):
print_formatted_text(HTML(f"<MediumSeaGreen>SLS auto-detected: {sls_default_namespace}</MediumSeaGreen>"))
if self.yesOrNo("Upload/Replace the license file"):
self.slsLicenseFileLocal = self.promptForFile("License file", mustExist=True, envVar="SLS_LICENSE_FILE_LOCAL")
else:
self.setParam("sls_action", "gencfg")

@logMethodCall
def configDRO(self) -> None:
self.promptForString("Contact e-mail address", "uds_contact_email")
self.promptForString("Contact first name", "uds_contact_firstname")
self.promptForString("Contact last name", "uds_contact_lastname")

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

@logMethodCall
Expand Down Expand Up @@ -730,6 +821,7 @@ def interactiveMode(self, simplified: bool, advanced: bool) -> None:

# Licensing (SLS and DRO)
self.configSLS()
self.configDRO()
self.configICRCredentials()

# MAS Core
Expand Down Expand Up @@ -896,10 +988,6 @@ def nonInteractiveMode(self) -> None:
if value is None:
self.fatalError(f"{key} must be set")
self.pipelineStorageClass = value
elif key == "license_file":
if value is None:
self.fatalError(f"{key} must be set")
self.slsLicenseFileLocal = value

elif key.startswith("approval_"):
if key not in self.approvals:
Expand Down Expand Up @@ -929,6 +1017,26 @@ def nonInteractiveMode(self) -> None:
self.setParam("mas_manual_cert_mgmt", False)
self.manualCertsDir = None

# SLS arguments
elif key == "sls_namespace":
if value is not None and value != "":
self.setParam("sls_namespace", value)
if findSLSByNamespace(value, dynClient= self.dynamicClient):
self.setParam("sls_action", "gencfg")
else:
self.setParam("sls_action", "install")
else:
self.setParam("sls_namespace", "ibm-sls")
self.setParam("sls_action", "gencfg")
elif key == "license_file":
if value is not None and value != "":
self.slsLicenseFileLocal = value
self.setParam("sls_action", "install")
elif key == "sls_certificates":
if value is not None and value != "":
self.slsCertsDirLocal = value
self.setParam("sls_action", "gencfg")

# Fail if there's any arguments we don't know how to handle
else:
print(f"Unknown option: {key} {value}")
Expand All @@ -941,13 +1049,16 @@ def nonInteractiveMode(self) -> None:
if not self.devMode:
self.validateCatalogSource()
self.licensePrompt()

self.validateExternalSLSConnection()

@logMethodCall
def install(self, argv):
"""
Install MAS instance
"""
args = installArgParser.parse_args(args=argv)
verifyArgs(installArgParser, args)

# We use the presence of --mas-instance-id to determine whether
# the CLI is being started in interactive mode or not
Expand All @@ -960,6 +1071,10 @@ def install(self, argv):
self.devMode = args.dev_mode
self.skipGrafanaInstall = args.skip_grafana_install

# Set image_pull_policy of the CLI in interactive mode
if args.image_pull_policy and args.image_pull_policy != "":
self.setParam("image_pull_policy", args.image_pull_policy)

self.approvals = {}

# Store all args
Expand Down Expand Up @@ -1007,13 +1122,10 @@ def install(self, argv):
if self.deployCP4D:
self.configCP4D()

# The entitlement file for SLS is mounted as a secret in /workspace/entitlement
entitlementFileBaseName = path.basename(self.slsLicenseFileLocal)
self.setParam("sls_entitlement_file", f"/workspace/entitlement/{entitlementFileBaseName}")

# Set up the secrets for additional configs, podtemplates and manual certificates
# Set up the secrets for additional configs, podtemplates, sls license file and manual certificates
self.additionalConfigs()
self.podTemplates()
self.slsLicenseFile()
self.manualCertificates()

# Show a summary of the installation configuration
Expand Down Expand Up @@ -1065,7 +1177,7 @@ def install(self, argv):
prepareInstallSecrets(
dynClient=self.dynamicClient,
instanceId=self.getParam("mas_instance_id"),
slsLicenseFile=self.slsLicenseFileLocal,
slsLicenseFile=self.slsLicenseFileSecret,
additionalConfigs=self.additionalConfigsSecret,
podTemplates=self.podTemplatesSecret,
certs=self.certsSecret
Expand Down
10 changes: 8 additions & 2 deletions python/src/mas/cli/install/argBuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,15 @@ def buildCommand(self) -> str:

# IBM Suite License Service
# -----------------------------------------------------------------------------
command += f" --license-file \"{self.slsLicenseFileLocal}\"{newline}"
if self.getParam("sls_namespace") != "ibm-sls":
if self.getParam("sls_namespace") and self.getParam("sls_namespace") != "ibm-sls":
command += f" --sls-namespace \"{self.getParam('sls_namespace')}\"{newline}"
if self.slsLicenseFileLocal:
command += f" --license-file \"{self.slsLicenseFileLocal}\"{newline}"
# External SLS configuration
if self.getParam("sls_url"):
command += f" --sls-url \"{self.getParam('sls_url')}\"{newline}"
command += f" --sls-registration-key \"{self.getParam('sls_registration_key')}\"{newline}"
command += f" --sls-certificates \"{self.slsCertsDirLocal}\"{newline}"

# IBM Data Reporting Operator (DRO)
# -----------------------------------------------------------------------------
Expand Down
27 changes: 27 additions & 0 deletions python/src/mas/cli/install/argChecker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# *****************************************************************************
# Copyright (c) 2025 IBM Corporation and other Contributors.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v1.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v10.html
#
# *****************************************************************************

import logging

logger = logging.getLogger(__name__)


def verifyArgs(parser, args):
verifySLSArgs(parser, args)

def verifySLSArgs(parser, args):
group_1 = [args.sls_namespace, args.license_file]
group_2 = [args.sls_url, args.sls_registration_key, args.sls_certificates]

if any(v is not None for v in group_1) and any(v is not None for v in group_2):
parser.error("Cannot combine [--sls-namespace, --license-file] with [--sls-url, --sls-registration-key, --sls-certificates].")

if not all(v is not None for v in group_2) and any(v is not None for v in group_2):
parser.error("When providing any of --sls-url, --sls-registration-key and --sls-certificates, all three are required.")
24 changes: 22 additions & 2 deletions python/src/mas/cli/install/argParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ def isValidFile(parser, arg) -> str:
else:
return arg

def isValidDir(parser, arg) -> str:
if not path.isdir(arg):
parser.error(f"Error: The directory {arg} does not exist")
else:
return arg

installArgParser = argparse.ArgumentParser(
prog="mas install",
Expand Down Expand Up @@ -260,8 +265,23 @@ def isValidFile(parser, arg) -> str:
slsArgGroup.add_argument(
"--sls-namespace",
required=False,
help="Customize the SLS install namespace",
default="ibm-sls"
help="Set namespace for new SLS install or point to existing instance",
)
slsArgGroup.add_argument(
"--sls-url",
required=False,
help="Point to the external SLS URL",
)
slsArgGroup.add_argument(
"--sls-registration-key",
required=False,
help="Set the SLS RegistrationKey",
)
slsArgGroup.add_argument(
"--sls-certificates",
required=False,
help="Path to SLS certificates",
type=lambda x: isValidDir(installArgParser, x)
)

# IBM Data Reporting Operator (DRO)
Expand Down
3 changes: 2 additions & 1 deletion python/src/mas/cli/install/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
"mas_appws_components",
"mas_domain",
# SLS
"sls_namespace",
"sls_url",
"sls_registration_key",
# DNS Providers
# TODO: Add CloudFlare and Route53 support
"dns_provider",
Expand Down
Loading
Loading