Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
140 changes: 132 additions & 8 deletions python/src/mas/cli/aiservice/install/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ def chooseInstallFlavour(self) -> None:
self.printDescription(
[
"There are two flavours of the interactive install to choose from: <u>Simplified</u> and <u>Advanced</u>. The simplified option will present fewer dialogs, but you lose the ability to configure the following aspects of the installation:",
" - Configure certificate issuer",
" - Enable IPv6 SingleStack networking for services",
" - Customize Scheduling configuration for AI workloads(Training pipeline & Inference services) for AI Service tenant",
" - Configure a custom domain and DNS integrations",
]
)
self.showAdvancedOptions = self.yesOrNo("Show advanced installation options")
Expand Down Expand Up @@ -267,6 +267,10 @@ def nonInteractiveMode(self) -> None:
self.setParam("aiservice_s3_ssl", "false")
self.setParam("aiservice_s3_region", "none")
self.setParam("aiservice_s3_bucket_prefix", "s3-")

# Set default bucket names
self.setParam("aiservice_s3_tenants_bucket", "km-tenants")
self.setParam("aiservice_s3_templates_bucket", "km-templates")
else:
self.fatalError(f"Unsupported value for --install-minio: {value}")

Expand Down Expand Up @@ -416,6 +420,10 @@ def nonInteractiveMode(self) -> None:
print(f"Unknown option: {key} {value}")
self.fatalError(f"Unknown option: {key} {value}")

# For CIS DNS provider, always set cis_entries_to_add to "aiservice" (stand-alone install)
if self.getParam("dns_provider") == "cis":
self.setParam("cis_entries_to_add", "aiservice")

# Load the catalog information
try:
self.chosenCatalog = getCatalog(self.getParam("mas_catalog_version"))
Expand Down Expand Up @@ -681,19 +689,135 @@ def aiServiceSettings(self) -> None:
"If an existing ODH installation is detected, the installer will automatically migrate it to RHOAI."
)

# Configure Certificate Issuer
self.configCertIssuer()
# Configure DNS
self.configDNSAndCerts()

# Configure Network configuration for services
self.configNetworking()

@logMethodCall
def configCertIssuer(self):
def configDNSAndCerts(self):
if self.showAdvancedOptions:
self.printH1("Configure Certificate Issuer")
configureCertIssuer = self.yesOrNo("Configure certificate issuer")
if configureCertIssuer:
self.promptForString("Certificate issuer name", "aiservice_certificate_issuer")
self.printH1("Cluster Ingress Secret Override")
self.printDescription(
[
"In most OpenShift clusters the installation is able to automatically locate the default ingress certificate, however in some configurations it is necessary to manually configure the name of the secret",
"Unless you see an error during the ocp-verify stage indicating that the secret can not be determined you do not need to set this and can leave the response empty",
]
)
self.promptForString(
"Cluster ingress certificate secret name",
"ocp_ingress_tls_secret_name",
default="",
)

self.printH1("Configure Domain & Certificate Management")
configureDomainAndCertMgmt = self.yesOrNo("Configure domain & certificate management")
if configureDomainAndCertMgmt:
configureDomain = self.yesOrNo("Configure custom domain")
if configureDomain:
self.promptForString("AI Service domain", "aiservice_domain")

self.printDescription(
[
"",
"DNS Integrations:",
" 1. IBM Cloud Internet Services",
" 2. AWS Route 53",
" 3. None (I will set up DNS myself)",
]
)
dnsProvider = self.promptForInt("DNS Provider", min=1, max=3)
if dnsProvider == 1:
self.configDNSAndCertsCIS()
elif dnsProvider == 2:
self.configDNSAndCertsRoute53()
elif dnsProvider == 3:
# Use self-signed certificate Issuer with custom domain
self.setParam("dns_provider", "")
self.setParam("aiservice_certificate_issuer", "")

if dnsProvider == 1:
self.printDescription(
[
"By default, DNS CNAME records will be created pointing to the domain of the cluster ingress (ingress.config.openshift.io/cluster).",
"CIS DNS integrations support the ability to provide an alternative domain, which may be necessary if you are using OpenShift Container Platform in a non-standard networking configuration.",
]
)
self.promptForString("Cluster Ingress Domain Override", "ocp_ingress")

else:
# Use self-signed certificate Issuer with default domain
self.setParam("dns_provider", "")
self.setParam("aiservice_domain", "")
self.setParam("aiservice_certificate_issuer", "")

@logMethodCall
def configDNSAndCertsCIS(self):
self.setParam("dns_provider", "cis")
self.promptForString("CIS e-mail", "cis_email")
self.promptForString("CIS API token", "cis_apikey", isPassword=True)
self.promptForString("CIS CRN", "cis_crn")
self.promptForString("CIS subdomain", "cis_subdomain")

self.printDescription(
[
"Certificate Issuer:",
" 1. LetsEncrypt (Production)",
" 2. LetsEncrypt (Staging)",
" 3. Self-Signed",
]
)
certIssuer = self.promptForInt("Certificate issuer", min=1, max=3)
certIssuerOptions = [
f"{self.getParam('aiservice_instance_id')}-cis-le-prod",
f"{self.getParam('aiservice_instance_id')}-cis-le-stg",
"",
]
self.setParam("aiservice_certificate_issuer", certIssuerOptions[certIssuer - 1])

# CIS security & behaviour options
configEnhancedSecurity = self.yesOrNo("Configure enhanced security for CIS", "cis_enhanced_security")
if configEnhancedSecurity:
self.printDescription(["Enter the name of your CIS service from IBM Cloud"])
self.promptForString("CIS service name", "cis_service_name")
self.yesOrNo("Update existing CIS DNS entries", "update_dns_entries")
self.yesOrNo("Enable WAF (Web Application Firewall)", "cis_waf")
self.yesOrNo("Enable CIS proxy", "cis_proxy")
self.yesOrNo("Delete wildcard DNS entries in CIS", "delete_wildcards")
self.yesOrNo("Override and delete existing edge certificates in CIS instance", "override_edge_certs")

# AI Service stand-alone install: always add only AI Service CIS entries
self.setParam("cis_entries_to_add", "aiservice")

@logMethodCall
def configDNSAndCertsRoute53(self):
self.setParam("dns_provider", "route53")
self.printDescription(
[
"Provide your AWS account access key ID and secret access key",
"This will be used to authenticate into the AWS account where your AWS Route 53 hosted zone instance is located",
"",
]
)
self.promptForString("AWS Access Key ID", "aws_access_key_id", isPassword=True)
self.promptForString("AWS Secret Access Key", "aws_secret_access_key", isPassword=True)

self.printDescription(
[
"Provide your AWS Route 53 hosted zone instance details",
"This information will be used to create webhook resources between your cluster and your AWS Route 53 instance (cluster issuer and cname records)",
"in order for it to be able to resolve DNS entries for all the subdomains created for your Maximo Application Suite instance",
"",
"Therefore, the AWS Route 53 subdomain + the AWS Route 53 hosted zone name defined, when combined, needs to match with the chosen AI Service domain, otherwise the DNS records won't be able to get resolved",
]
)
self.promptForString("AWS Route 53 hosted zone name", "route53_hosted_zone_name")
self.promptForString("AWS Route 53 hosted zone region", "route53_hosted_zone_region")
self.promptForString("AWS Route 53 subdomain", "route53_subdomain")
self.promptForString("AWS Route 53 e-mail", "route53_email")

self.setParam("aiservice_certificate_issuer", f"{self.getParam('aiservice_instance_id')}-route53-le-prod")

@logMethodCall
def configNetworking(self):
Expand Down
111 changes: 87 additions & 24 deletions python/src/mas/cli/aiservice/install/argBuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,32 @@ def buildCommand(self) -> str:
command += "export IBMCLOUD_APIKEY=x\n"
if self.getParam("aws_access_key_id") != "":
command += "export AWS_ACCESS_KEY_ID=x\n"
if self.getParam("secret_access_key") != "":
command += "export SECRET_ACCESS_KEY=x\n"
if self.getParam("aws_secret_access_key") != "":
command += "export AWS_SECRET_ACCESS_KEY=x\n"
if self.getParam("artifactory_username") != "":
command += "export ARTIFACTORY_USERNAME=x\nexport ARTIFACTORY_TOKEN=x\n"

# Object Storage Credentials
if self.getParam("minio_root_user") != "" and self.getParam("minio_root_password") != "":
command += "export MINIO_ROOT_USER=x\n"
command += "export MINIO_ROOT_PASSWORD=x\n"
else:
if self.getParam("aiservice_s3_accesskey") != "":
command += "export AISERVICE_S3_ACCESSKEY=x\n"
if self.getParam("aiservice_s3_secretkey") != "":
command += "export AISERVICE_S3_SECRETKEY=x\n"

# Watsonx Credentials
if self.getParam("aiservice_watsonxai_apikey") != "":
command += "export AISERVICE_WATSONXAI_APIKEY=x\n"

if self.getParam("cis_apikey") != "":
command += "export CIS_APIKEY=x\n"

# Database password
if self.getParam("aiservice_db_password") != "":
command += "export AISERVICE_DB_PASSWORD=x\n"

command += f"mas aiservice-install --mas-catalog-version {self.getParam('mas_catalog_version')}"

if self.getParam("mas_catalog_digest") != "":
Expand All @@ -38,7 +59,7 @@ def buildCommand(self) -> str:
# AI Service Instance Id
command += f" --aiservice-instance-id \"{self.getParam('aiservice_instance_id')}\"{newline}"

# MAS Advanced Configuration
# AI Service Advanced Configuration
# -----------------------------------------------------------------------------

if self.localConfigDir is not None:
Expand Down Expand Up @@ -101,31 +122,78 @@ def buildCommand(self) -> str:
if self.getParam("service_account_name") != "":
command += f" --service-account {self.getParam('service_account_name')}{newline}"

# OCP Configuration
# -----------------------------------------------------------------------------
if self.getParam("ocp_ingress_tls_secret_name") != "":
command += f" --ocp-ingress-tls-secret-name \"{self.getParam('ocp_ingress_tls_secret_name')}\"{newline}"
if self.getParam("ocp_ingress") != "":
command += f" --ocp-ingress \"{self.getParam('ocp_ingress')}\"{newline}"

# AI Service Advanced Settings
# -----------------------------------------------------------------------------

# Certificate Issuer
if self.getParam("aiservice_certificate_issuer") != "":
command += f" --aiservice-certificate-issuer \"{self.getParam('aiservice_certificate_issuer')}\"{newline}"

if self.getParam("aiservice_domain") != "":
command += f" --domain \"{self.getParam('aiservice_domain')}\"{newline}"

if self.getParam("dns_provider") == "cis":
command += f" --dns-provider cis{newline}"
command += f' --cis-apikey "$CIS_APIKEY"{newline}'
command += f" --cis-subdomain \"{self.getParam('cis_subdomain')}\"{newline}"
command += f" --cis-crn \"{self.getParam('cis_crn')}\"{newline}"
command += f" --cis-email \"{self.getParam('cis_email')}\"{newline}"
if self.getParam("cis_enhanced_security") == "true":
command += f" --cis-enhanced-security{newline}"
if self.getParam("cis_service_name") != "":
command += f" --cis-service-name \"{self.getParam('cis_service_name')}\"{newline}"
if self.getParam("update_dns_entries") == "true":
command += f" --update-dns-entries{newline}"
if self.getParam("cis_waf") == "true":
command += f" --cis-waf{newline}"
if self.getParam("cis_proxy") == "true":
command += f" --cis-proxy{newline}"
if self.getParam("delete_wildcards") == "true":
command += f" --delete-wildcards{newline}"
if self.getParam("override_edge_certs") == "true":
command += f" --override-edge-certs{newline}"

if self.getParam("dns_provider") == "route53":
command += f" --dns-provider route53{newline}"
command += f" --route53-subdomain \"{self.getParam('route53_subdomain')}\"{newline}"
command += f" --route53-email \"{self.getParam('route53_email')}\"{newline}"
command += f" --route53-hosted-zone-name \"{self.getParam('route53_hosted_zone_name')}\"{newline}"
command += f" --route53-hosted-zone-region \"{self.getParam('route53_hosted_zone_region')}\"{newline}"
command += f' --aws-access-key-id "$AWS_ACCESS_KEY_ID"{newline}'
command += f' --aws-secret-access-key "$AWS_SECRET_ACCESS_KEY"{newline}'

# Enable IPv6 networking
if self.getParam("enable_ipv6").lower() == "true":
command += f" --enable-ipv6{newline}"

if self.getParam("aiservice_s3_accesskey") != "":
command += f" --s3-accesskey \"{self.getParam('aiservice_s3_accesskey')}\"{newline}"
if self.getParam("aiservice_s3_secretkey") != "":
command += f" --s3-secretkey \"{self.getParam('aiservice_s3_secretkey')}\"{newline}"
if self.getParam("aiservice_s3_host") != "":
command += f" --s3-host \"{self.getParam('aiservice_s3_host')}\"{newline}"
if self.getParam("aiservice_s3_port") != "":
command += f" --s3-port \"{self.getParam('aiservice_s3_port')}\"{newline}"
if self.getParam("aiservice_s3_ssl") != "":
command += f" --s3-ssl \"{self.getParam('aiservice_s3_ssl')}\"{newline}"
if self.getParam("aiservice_s3_region") != "":
command += f" --s3-region \"{self.getParam('aiservice_s3_region')}\"{newline}"
if self.getParam("aiservice_s3_bucket_prefix") != "":
command += f" --s3-bucket-prefix \"{self.getParam('aiservice_s3_bucket_prefix')}\"{newline}"
# Object storage
if self.getParam("minio_root_user") != "" and self.getParam("minio_root_password") != "":
command += f" --install-minio{newline}"
command += f' --minio-root-user "$MINIO_ROOT_USER"{newline}'
command += f' --minio-root-password "$MINIO_ROOT_PASSWORD"{newline}'
else:
if self.getParam("aiservice_s3_accesskey") != "":
command += f' --s3-accesskey "$AISERVICE_S3_ACCESSKEY"{newline}'
if self.getParam("aiservice_s3_secretkey") != "":
command += f' --s3-secretkey "$AISERVICE_S3_SECRETKEY"{newline}'
if self.getParam("aiservice_s3_host") != "":
command += f" --s3-host \"{self.getParam('aiservice_s3_host')}\"{newline}"
if self.getParam("aiservice_s3_port") != "":
command += f" --s3-port \"{self.getParam('aiservice_s3_port')}\"{newline}"
if self.getParam("aiservice_s3_ssl") != "":
command += f" --s3-ssl \"{self.getParam('aiservice_s3_ssl')}\"{newline}"
if self.getParam("aiservice_s3_region") != "":
command += f" --s3-region \"{self.getParam('aiservice_s3_region')}\"{newline}"
if self.getParam("aiservice_s3_bucket_prefix") != "":
command += f" --s3-bucket-prefix \"{self.getParam('aiservice_s3_bucket_prefix')}\"{newline}"

if self.getParam("aiservice_s3_tenants_bucket") != "":
command += f" --s3-tenants-bucket \"{self.getParam('aiservice_s3_tenants_bucket')}\"{newline}"
if self.getParam("aiservice_s3_templates_bucket") != "":
Expand All @@ -139,7 +207,7 @@ def buildCommand(self) -> str:
command += f" --rhoai{newline}"

if self.getParam("aiservice_watsonxai_apikey") != "":
command += f" --watsonxai-apikey \"{self.getParam('aiservice_watsonxai_apikey')}\"{newline}"
command += f' --watsonxai-apikey "$AISERVICE_WATSONXAI_APIKEY"{newline}'
if self.getParam("aiservice_watsonxai_url") != "":
command += f" --watsonxai-url \"{self.getParam('aiservice_watsonxai_url')}\"{newline}"
if self.getParam("aiservice_watsonxai_project_id") != "":
Expand All @@ -161,11 +229,6 @@ def buildCommand(self) -> str:
if self.getParam("aiservice_watsonxai_on_prem") != "":
command += f" --watsonxai-onprem \"{self.getParam('aiservice_watsonxai_on_prem')}\"{newline}"

if self.getParam("minio_root_user") != "":
command += f" --minio-root-user \"{self.getParam('minio_root_user')}\"{newline}"
if self.getParam("minio_root_password") != "":
command += f" --minio-root-password \"{self.getParam('minio_root_password')}\"{newline}"

if self.getParam("tenant_entitlement_type") != "":
command += f" --tenant-entitlement-type \"{self.getParam('tenant_entitlement_type')}\"{newline}"
if self.getParam("tenant_entitlement_start_date") != "":
Expand Down Expand Up @@ -193,7 +256,7 @@ def buildCommand(self) -> str:
# External database (Oracle/SQL Server/DB2)
command += f" --aiservice-db-jdbc-url \"{self.getParam('aiservice_db_jdbc_url')}\"{newline}"
command += f" --aiservice-db-username \"{self.getParam('aiservice_db_username')}\"{newline}"
command += f" --aiservice-db-password \"{self.getParam('aiservice_db_password')}\"{newline}"
command += f' --aiservice-db-password "$AISERVICE_DB_PASSWORD"{newline}'
if self.getParam("aiservice_db_ca_cert") != "":
command += f" --aiservice-db-ca-cert \"{self.getParam('aiservice_db_ca_cert')}\"{newline}"

Expand Down
Loading
Loading