From f13d9279fc6f7035f85b67f3bf7d9c1e338072de Mon Sep 17 00:00:00 2001 From: terc1997 <64480693+terc1997@users.noreply.github.com> Date: Fri, 26 Dec 2025 11:24:03 -0300 Subject: [PATCH 1/3] [patch] implement cluster issuer override --- python/src/mas/cli/install/app.py | 66 +++++++++++++++++-------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/python/src/mas/cli/install/app.py b/python/src/mas/cli/install/app.py index 531dce149e7..d9d9423f585 100644 --- a/python/src/mas/cli/install/app.py +++ b/python/src/mas/cli/install/app.py @@ -519,7 +519,12 @@ def configDNSAndCerts(self): "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("Override Cluster Issuer") + self.printDescription([ + "The cluster issuer is defined by the DNS Provider. This option is to override the configuration of the DNS provider if you're using a custom cluster issuer", + "If you're not using a custom cluster issuer, you can leave the response empty." + ]) + self.promptForString("Cluster Issuer Name", "mas_cluster_issuer", default="") self.printH1("Configure Domain & Certificate Management") configureDomainAndCertMgmt = self.yesOrNo('Configure domain & certificate management') if configureDomainAndCertMgmt: @@ -546,7 +551,6 @@ def configDNSAndCerts(self): elif dnsProvider == 4: # Use MAS default self-signed cluster issuer with a custom domain self.setParam("dns_provider", "") - self.setParam("mas_cluster_issuer", "") if dnsProvider in [1, 2]: self.printDescription([ @@ -559,7 +563,6 @@ def configDNSAndCerts(self): # Use MAS default self-signed cluster issuer with the default domain self.setParam("dns_provider", "") self.setParam("mas_domain", "") - self.setParam("mas_cluster_issuer", "") self.manualCerts = self.yesOrNo("Configure manual certificates") self.setParam("mas_manual_cert_mgmt", self.manualCerts) if self.getParam("mas_manual_cert_mgmt"): @@ -576,19 +579,20 @@ def configDNSAndCertsCloudflare(self): self.promptForString("Cloudflare zone", "cloudflare_zone") self.promptForString("Cloudflare subdomain", "cloudflare_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('mas_instance_id')}-cloudflare-le-prod", - f"{self.getParam('mas_instance_id')}-cloudflare-le-stg", - "" - ] - self.setParam("mas_cluster_issuer", certIssuerOptions[certIssuer - 1]) + if not self.getParam("mas_cluster_issuer"): + 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('mas_instance_id')}-cloudflare-le-prod", + f"{self.getParam('mas_instance_id')}-cloudflare-le-stg", + "" + ] + self.setParam("mas_cluster_issuer", certIssuerOptions[certIssuer - 1]) @logMethodCall def configDNSAndCertsCIS(self): @@ -598,19 +602,20 @@ def configDNSAndCertsCIS(self): 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('mas_instance_id')}-cis-le-prod", - f"{self.getParam('mas_instance_id')}-cis-le-stg", - "" - ] - self.setParam("mas_cluster_issuer", certIssuerOptions[certIssuer - 1]) + if not self.getParam("mas_cluster_issuer"): + 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('mas_instance_id')}-cis-le-prod", + f"{self.getParam('mas_instance_id')}-cis-le-stg", + "" + ] + self.setParam("mas_cluster_issuer", certIssuerOptions[certIssuer - 1]) @logMethodCall def configDNSAndCertsRoute53(self): @@ -635,7 +640,8 @@ def configDNSAndCertsRoute53(self): self.promptForString("AWS Route 53 subdomain", "route53_subdomain") self.promptForString("AWS Route 53 e-mail", "route53_email") - self.setParam("mas_cluster_issuer", f"{self.getParam('mas_instance_id')}-route53-le-prod") + if not self.getParam("mas_cluster_issuer"): + self.setParam("mas_cluster_issuer", f"{self.getParam('mas_instance_id')}-route53-le-prod") @logMethodCall def configApps(self): From 5f46485eeb1a9cdb57252a66214e7b4734c98213 Mon Sep 17 00:00:00 2001 From: terc1997 <64480693+terc1997@users.noreply.github.com> Date: Tue, 30 Dec 2025 15:20:38 -0300 Subject: [PATCH 2/3] [patch] add condition to reuse deployed cluster issuer --- python/src/mas/cli/install/app.py | 33 ++++++++++++++++++++++++------- python/src/mas/cli/validators.py | 17 +++++++++++++++- 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/python/src/mas/cli/install/app.py b/python/src/mas/cli/install/app.py index 7fe365fd4eb..7ba08156d3d 100644 --- a/python/src/mas/cli/install/app.py +++ b/python/src/mas/cli/install/app.py @@ -37,6 +37,7 @@ from .catalogs import supportedCatalogs from mas.cli.validators import ( + ClusterIssuerValidator, InstanceIDFormatValidator, WorkspaceIDFormatValidator, WorkspaceNameFormatValidator, @@ -51,7 +52,8 @@ createNamespace, getStorageClasses, getClusterVersion, - isClusterVersionInRange + isClusterVersionInRange, + getClusterIssuers ) from mas.devops.mas import ( getCurrentCatalog, @@ -537,12 +539,6 @@ def configDNSAndCerts(self): "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("Override Cluster Issuer") - self.printDescription([ - "The cluster issuer is defined by the DNS Provider. This option is to override the configuration of the DNS provider if you're using a custom cluster issuer", - "If you're not using a custom cluster issuer, you can leave the response empty." - ]) - self.promptForString("Cluster Issuer Name", "mas_cluster_issuer", default="") self.printH1("Configure Domain & Certificate Management") configureDomainAndCertMgmt = self.yesOrNo('Configure domain & certificate management') if configureDomainAndCertMgmt: @@ -569,6 +565,7 @@ def configDNSAndCerts(self): elif dnsProvider == 4: # Use MAS default self-signed cluster issuer with a custom domain self.setParam("dns_provider", "") + self.setParam("mas_cluster_issuer", "") if dnsProvider in [1, 2]: self.printDescription([ @@ -587,6 +584,28 @@ def configDNSAndCerts(self): self.manualCertsDir = self.promptForDir("Enter the path containing the manual certificates", mustExist=True) else: self.manualCertsDir = None + else: + # Configuring domain + if self.yesOrNo('Configure custom domain'): + self.promptForString("MAS top-level domain", "mas_domain") + else: + self.setParam("mas_domain", "") + + # Configuring DNS + if self.yesOrNo("Do you want MAS to set up its own (self-signed) cluster issuer?"): + self.setParam("mas_cluster_issuer", "") + else: + self.printDescription([ + "Select the ClusterIssuer to use from the list below:", + ]) + clusterIssuers = getClusterIssuers(self.dynamicClient) + if clusterIssuers is not None and len(clusterIssuers) > 0: + for clusterIssuer in clusterIssuers: + print_formatted_text(HTML(f" - {clusterIssuer.metadata.name}")) + self.params['mas_cluster_issuer'] = prompt(HTML('MAS Cluster Issuer '), validator=ClusterIssuerValidator(), validate_while_typing=False) + else: + print_formatted_text(HTML("No ClusterIssuers found on this cluster. MAS will use self-signed certificates.")) + self.setParam("mas_cluster_issuer", "") @logMethodCall def configDNSAndCertsCloudflare(self): diff --git a/python/src/mas/cli/validators.py b/python/src/mas/cli/validators.py index 446349b63b0..202dc432b8e 100644 --- a/python/src/mas/cli/validators.py +++ b/python/src/mas/cli/validators.py @@ -19,7 +19,7 @@ from prompt_toolkit.validation import Validator, ValidationError -from mas.devops.ocp import getStorageClass +from mas.devops.ocp import getStorageClass, getClusterIssuer from mas.devops.mas import verifyMasInstance from mas.devops.aiservice import verifyAiServiceInstance, verifyAiServiceTenantInstance @@ -238,3 +238,18 @@ def validate(self, document): if not match(r"^.{1,4}$", bucketPrefix): raise ValidationError(message='Bucket prefix does not meet the requirement', cursor_position=len(bucketPrefix)) + + +class ClusterIssuerValidator(Validator): + def validate(self, document): + """ + Validate that a ClusterIssuer exists on the target cluster + """ + name = document.text + + dynClient = dynamic.DynamicClient( + api_client.ApiClient(configuration=config.load_kube_config()) + ) + + if getClusterIssuer(dynClient, name) is None: + raise ValidationError(message='Specified cluster issuer is not available on this cluster', cursor_position=len(name)) From b19160bebaf4fdef7e53e115714018d5ddb0d9b2 Mon Sep 17 00:00:00 2001 From: terc1997 <64480693+terc1997@users.noreply.github.com> Date: Tue, 30 Dec 2025 15:24:29 -0300 Subject: [PATCH 3/3] [patch] remove the override option for cluster issuer --- python/src/mas/cli/install/app.py | 57 +++++++++++++++---------------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/python/src/mas/cli/install/app.py b/python/src/mas/cli/install/app.py index 7ba08156d3d..8d72b21c31a 100644 --- a/python/src/mas/cli/install/app.py +++ b/python/src/mas/cli/install/app.py @@ -616,20 +616,19 @@ def configDNSAndCertsCloudflare(self): self.promptForString("Cloudflare zone", "cloudflare_zone") self.promptForString("Cloudflare subdomain", "cloudflare_subdomain") - if not self.getParam("mas_cluster_issuer"): - 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('mas_instance_id')}-cloudflare-le-prod", - f"{self.getParam('mas_instance_id')}-cloudflare-le-stg", - "" - ] - self.setParam("mas_cluster_issuer", certIssuerOptions[certIssuer - 1]) + 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('mas_instance_id')}-cloudflare-le-prod", + f"{self.getParam('mas_instance_id')}-cloudflare-le-stg", + "" + ] + self.setParam("mas_cluster_issuer", certIssuerOptions[certIssuer - 1]) @logMethodCall def configDNSAndCertsCIS(self): @@ -639,20 +638,19 @@ def configDNSAndCertsCIS(self): self.promptForString("CIS CRN", "cis_crn") self.promptForString("CIS subdomain", "cis_subdomain") - if not self.getParam("mas_cluster_issuer"): - 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('mas_instance_id')}-cis-le-prod", - f"{self.getParam('mas_instance_id')}-cis-le-stg", - "" - ] - self.setParam("mas_cluster_issuer", certIssuerOptions[certIssuer - 1]) + 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('mas_instance_id')}-cis-le-prod", + f"{self.getParam('mas_instance_id')}-cis-le-stg", + "" + ] + self.setParam("mas_cluster_issuer", certIssuerOptions[certIssuer - 1]) @logMethodCall def configDNSAndCertsRoute53(self): @@ -677,8 +675,7 @@ def configDNSAndCertsRoute53(self): self.promptForString("AWS Route 53 subdomain", "route53_subdomain") self.promptForString("AWS Route 53 e-mail", "route53_email") - if not self.getParam("mas_cluster_issuer"): - self.setParam("mas_cluster_issuer", f"{self.getParam('mas_instance_id')}-route53-le-prod") + self.setParam("mas_cluster_issuer", f"{self.getParam('mas_instance_id')}-route53-le-prod") @logMethodCall def configApps(self):