diff --git a/python/src/mas/cli/install/app.py b/python/src/mas/cli/install/app.py index fb72f6d17ff..242a49e1bae 100644 --- a/python/src/mas/cli/install/app.py +++ b/python/src/mas/cli/install/app.py @@ -944,6 +944,19 @@ def configRoutingMode(self): else: self.setParam("mas_routing_mode", "subdomain") + # After routing mode is confirmed as path, offer Let's Encrypt HTTP-01. + if self.getParam("mas_routing_mode") == "path": + self.printDescription( + [ + "", + "Let's Encrypt HTTP-01 can automatically issue trusted certificates for your domain", + "Note: your cluster must be publicly accessible on port 80 from the internet.", + ] + ) + if self.yesOrNo("Do you want to use Let's Encrypt for certificate management"): + self.promptForString("Let's Encrypt e-mail", "mas_le_email") + self.setParam("mas_cluster_issuer", f"{self.getParam('mas_instance_id')}-http01-le-prod") + def _checkIngressControllerForPathRouting(self, controllerName="default"): """Check if a specific IngressController exists and is configured for path-based routing. @@ -2909,6 +2922,26 @@ def install(self, argv): else: logger.info(f"IngressController '{ingressControllerName}' is already configured for path-based routing") + # Validate Let's Encrypt HTTP-01 flags + if not self.isInteractiveMode: + leEmail = self.getParam("mas_le_email") + + if leEmail and self.getParam("mas_routing_mode") != "path": + self.fatalError( + "\n".join( + [ + "Let's Encrypt HTTP-01 Requires Path-Based Routing", + "========================================================================", + "--le-email is only supported with path-based routing mode.", + "Remove --le-email or switch to path routing:", + " mas install --routing path --le-email [email] ...", + ] + ) + ) + + if leEmail and not self.getParam("mas_cluster_issuer"): + self.setParam("mas_cluster_issuer", f"{self.getParam('mas_instance_id')}-http01-le-prod") + # Based on the parameters set the annotations correctly self.configAnnotations() self.displayInstallSummary() diff --git a/python/src/mas/cli/install/argParser.py b/python/src/mas/cli/install/argParser.py index bc0a5c6850c..52eaeffc97a 100644 --- a/python/src/mas/cli/install/argParser.py +++ b/python/src/mas/cli/install/argParser.py @@ -243,6 +243,13 @@ def isValidFile(parser: argparse.ArgumentParser, arg: str) -> str: help="Provide the name of the ClusterIssuer to configure MAS to issue certificates", ) +masAdvancedArgGroup.add_argument( + "--le-email", + dest="mas_le_email", + required=False, + help="E-mail address to register with Let's Encrypt. Providing this implicitly enables Let's Encrypt HTTP-01 certificate management (path routing mode only, cluster must be publicly accessible on port 80)", +) + masAdvancedArgGroup.add_argument( "--mas-issuer-kind", dest="mas_issuer_kind", diff --git a/python/src/mas/cli/install/params.py b/python/src/mas/cli/install/params.py index ef292b2a0e9..51fab7bf3d3 100644 --- a/python/src/mas/cli/install/params.py +++ b/python/src/mas/cli/install/params.py @@ -75,6 +75,8 @@ "dns_provider", "mas_cluster_issuer", "ocp_ingress", + # Let's Encrypt HTTP-01 + "mas_le_email", # CIS "cis_email", "cis_apikey", diff --git a/python/src/mas/cli/install/summarizer.py b/python/src/mas/cli/install/summarizer.py index 0bda7a81902..270f85cf511 100644 --- a/python/src/mas/cli/install/summarizer.py +++ b/python/src/mas/cli/install/summarizer.py @@ -153,6 +153,12 @@ def masSummary(self) -> None: if self.getParam("mas_routing_mode") == "path": self.printParamSummary("IngressController Name", "mas_ingress_controller_name") self.printParamSummary("Configure IngressController", "mas_configure_ingress") + if self.getParam("mas_le_email") != "": + self.printSummary("Let's Encrypt HTTP-01", "Enabled") + self.printParamSummary("Let's Encrypt e-mail", "mas_le_email") + self.printParamSummary("Let's Encrypt Issuer", "mas_cluster_issuer") + else: + self.printSummary("Let's Encrypt HTTP-01", "Disabled") if self.getParam("mas_manual_route_mgmt") == "true": self.printParamSummary("Manual Routes", "mas_manual_route_mgmt") diff --git a/python/tests/integration/install/test_arcgis_cluster_mode.py b/python/tests/integration/install/test_arcgis_cluster_mode.py old mode 100644 new mode 100755 index fb065046b4d..24d67f1a555 --- a/python/tests/integration/install/test_arcgis_cluster_mode.py +++ b/python/tests/integration/install/test_arcgis_cluster_mode.py @@ -35,7 +35,10 @@ def test_install_arcgis_cluster_mode_success(tmpdir): ".*Select catalog.*": lambda msg: "v9-master-amd64", ".*Select channel.*": lambda msg: "9.2.x-dev", # Use 9.2.x-dev channel # 4. Routing Mode Configuration - Select subdomain routing (simplest for this test) - ".*Routing Mode.*": lambda msg: "2", # Select subdomain routing + ".*Routing Mode.*": lambda msg: "2", # Select subdomain routing, 1 for path-based routing + # Note: IngressController selection prompt does NOT appear because there's only one controller + # 4.1. Let's Encrypt HTTP-01 - decline, use existing cert setup + # ".*Do you want to use Let.*s Encrypt.*": lambda msg: "n", # 5. Service Mesh Configuration ".*Enable OpenShift Service Mesh support for MAS.*": lambda msg: "y", # 5. Storage classes @@ -197,6 +200,8 @@ def test_install_arcgis_namespace_mode_error(tmpdir, caplog): ".*Select channel.*": lambda msg: "9.2.x-dev", # 4. Routing Mode Configuration ".*Routing Mode.*": lambda msg: "1", + # 4.1. Let's Encrypt HTTP-01 - decline, use existing cert setup + ".*Do you want to use Let.*s Encrypt.*": lambda msg: "n", # 5. Service Mesh Configuration ".*Enable OpenShift Service Mesh support for MAS.*": lambda msg: "y", # 6. Configure IngressController @@ -338,6 +343,8 @@ def test_install_arcgis_minimal_mode_error(tmpdir, caplog): ".*Select channel.*": lambda msg: "9.2.x-dev", # 4. Routing Mode Configuration ".*Routing Mode.*": lambda msg: "1", + # 4.1. Let's Encrypt HTTP-01 - decline, use existing cert setup + ".*Do you want to use Let.*s Encrypt.*": lambda msg: "n", # 5. Service Mesh Configuration ".*Enable OpenShift Service Mesh support for MAS.*": lambda msg: "y", # 6. Configure IngressController diff --git a/python/tests/integration/install/test_dev_mode.py b/python/tests/integration/install/test_dev_mode.py index 180b88cf6e2..22c6f423cf3 100644 --- a/python/tests/integration/install/test_dev_mode.py +++ b/python/tests/integration/install/test_dev_mode.py @@ -250,7 +250,9 @@ def test_install_master_dev_mode_with_path_routing(tmpdir): # 4. Routing Mode Configuration - Select path-based routing ".*Routing Mode.*": lambda msg: "1", # Select path-based routing # Note: IngressController selection prompt does NOT appear because there's only one controller - # 5. Service Mesh Configuration - to use service mesg + # 4.1. Let's Encrypt HTTP-01 - decline, use existing cert setup + ".*Do you want to use Let.*s Encrypt.*": lambda msg: "n", + # 5. Service Mesh Configuration - to use service mesh ".*Enable OpenShift Service Mesh support for MAS.*": lambda msg: "y", # Select to use service mesh # 5. Configure IngressController for path-based routing ".*Configure ingress namespace ownership.*": lambda msg: "y", # Agree to configure diff --git a/python/tests/integration/install/test_letsencrypt.py b/python/tests/integration/install/test_letsencrypt.py new file mode 100644 index 00000000000..6c3b63aa7d0 --- /dev/null +++ b/python/tests/integration/install/test_letsencrypt.py @@ -0,0 +1,429 @@ +#!/usr/bin/env python +# ***************************************************************************** +# Copyright (c) 2026 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 +# +# ***************************************************************************** + +""" +Test suite for Let's Encrypt HTTP-01 certificate management in MAS CLI. + +Covers: + Interactive mode: + - User opts in → email collected, mas_cluster_issuer set + - User opts out → email stays empty, no email prompt + - Only shown when routing mode is path (subdomain skips it entirely) + + Non-interactive mode: + - --le-email + --routing path → valid, mas_cluster_issuer auto-set + - --le-email + --routing subdomain → fatalError + - --le-email + --mas-cluster-issuer custom → custom issuer respected + - no --le-email → LE disabled (Ansible derives from empty email) +""" + +import sys +import os +import pytest +from unittest.mock import MagicMock +from mas.cli.install.app import InstallApp +from mas.cli.install.argParser import installArgParser +from utils.install_test_helper import InstallTestConfig, run_install_test + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) + +# ============================================================================= +# Helpers (mirrors pattern in test_routing_mode.py) +# ============================================================================= + + +def create_mock_app(instance_id="testinst"): + """Create a mock InstallApp suitable for unit-level LE tests.""" + app = MagicMock(spec=InstallApp) + app.dynamicClient = MagicMock() + app.showAdvancedOptions = True + app.isInteractiveMode = True + app.params = {} + app.setParam = lambda key, value: app.params.__setitem__(key, value) + app.getParam = lambda key: app.params.get(key, "") + + app.promptForInt = MagicMock(return_value=1) + app.yesOrNo = MagicMock(return_value=True) + app.printDescription = MagicMock() + app.printH1 = MagicMock() + + # promptForString side-effect: store supplied value and return it + def _prompt_for_string(label, param, **kwargs): + # simulate user entering the label text as the value + val = f"user-{param}" + app.params[param] = val + return val + + app.promptForString = MagicMock(side_effect=_prompt_for_string) + app.fatalError = MagicMock(side_effect=SystemExit(1)) + + # Seed required params + app.params["mas_instance_id"] = instance_id + app.params["mas_routing_mode"] = "path" + + # Bind the real method under test + app.configRoutingMode = InstallApp.configRoutingMode.__get__(app, InstallApp) + app._checkIngressControllerPermissions = MagicMock(return_value=True) + app._checkIngressControllerForPathRouting = MagicMock(return_value=(True, True)) + app._promptForIngressController = MagicMock(return_value="default") + app._getMasDomainForDisplay = MagicMock(return_value="example.com") + + return app + + +# ============================================================================= +# Interactive Mode Tests +# ============================================================================= + + +class TestInteractiveLetsEncrypt: + """Interactive prompt flow for Let's Encrypt HTTP-01.""" + + def test_user_opts_in_collects_email_and_sets_cluster_issuer(self): + """When user says yes to LE, email is collected and mas_cluster_issuer is set.""" + app = create_mock_app(instance_id="inst1") + app.params["mas_routing_mode"] = "path" + app.yesOrNo = MagicMock(return_value=True) + + # Replicate the current interactive LE block from configRoutingMode + if app.getParam("mas_routing_mode") == "path": + app.printDescription([]) + if app.yesOrNo("Do you want to use Let's Encrypt for certificate management"): + app.promptForString("Let's Encrypt e-mail", "mas_le_email") + app.setParam("mas_cluster_issuer", f"{app.getParam('mas_instance_id')}-http01-le-prod") + + assert app.getParam("mas_le_email") == "user-mas_le_email" + assert app.getParam("mas_cluster_issuer") == "inst1-http01-le-prod" + app.promptForString.assert_called_once() + + def test_user_opts_out_no_email_and_no_cluster_issuer(self): + """When user says no to LE, email stays empty and cluster issuer is not set.""" + app = create_mock_app(instance_id="inst1") + app.params["mas_routing_mode"] = "path" + app.yesOrNo = MagicMock(return_value=False) + + if app.getParam("mas_routing_mode") == "path": + app.printDescription([]) + if app.yesOrNo("Do you want to use Let's Encrypt for certificate management"): + app.promptForString("Let's Encrypt e-mail", "mas_le_email") + app.setParam("mas_cluster_issuer", f"{app.getParam('mas_instance_id')}-http01-le-prod") + + assert app.getParam("mas_le_email") == "" + assert app.getParam("mas_cluster_issuer") == "" + app.promptForString.assert_not_called() + + def test_le_prompt_not_shown_for_subdomain_routing(self): + """LE prompt must not appear when routing mode is subdomain.""" + app = create_mock_app() + app.params["mas_routing_mode"] = "subdomain" + app.yesOrNo = MagicMock(return_value=True) + + if app.getParam("mas_routing_mode") == "path": + app.printDescription([]) + if app.yesOrNo("Do you want to use Let's Encrypt for certificate management"): + app.promptForString("Let's Encrypt e-mail", "mas_le_email") + + # yesOrNo should never have been called (routing is subdomain) + app.yesOrNo.assert_not_called() + assert app.getParam("mas_le_email") == "" + + +# ============================================================================= +# Non-Interactive: argParser flag tests +# ============================================================================= + + +class TestArgParserLetsEncrypt: + """Verify --le-email is parsed correctly and is the sole LE enablement signal.""" + + def _base_argv(self): + return [ + "--mas-instance-id", + "testinst", + "--mas-workspace-id", + "testws", + "--mas-channel", + "9.2.0", + "--admin-mode", + "cluster", + "--routing", + "path", + "--accept-license", + "--no-confirm", + ] + + def test_le_email_enables_le(self): + """--le-email enables LE — it is the only flag needed.""" + argv = self._base_argv() + ["--le-email", "test@ibm.com"] + args = installArgParser.parse_args(args=argv) + assert args.mas_le_email == "test@ibm.com" + + def test_no_le_email_means_le_disabled(self): + """Without --le-email, mas_le_email is None — Ansible derives LE disabled.""" + argv = self._base_argv() + args = installArgParser.parse_args(args=argv) + assert args.mas_le_email is None + + def test_le_email_with_subdomain_routing_is_parsed(self): + """Parser accepts the combination; app validation rejects it.""" + argv = [ + "--mas-instance-id", + "testinst", + "--mas-workspace-id", + "testws", + "--mas-channel", + "9.2.0", + "--admin-mode", + "cluster", + "--routing", + "subdomain", + "--le-email", + "test@ibm.com", + "--accept-license", + "--no-confirm", + ] + args = installArgParser.parse_args(args=argv) + assert args.mas_routing_mode == "subdomain" + assert args.mas_le_email == "test@ibm.com" + + def test_custom_cluster_issuer_preserved_with_le_email(self): + """--mas-cluster-issuer is preserved alongside --le-email.""" + argv = self._base_argv() + [ + "--le-email", + "test@ibm.com", + "--mas-cluster-issuer", + "my-custom-issuer", + ] + args = installArgParser.parse_args(args=argv) + assert args.mas_le_email == "test@ibm.com" + assert args.mas_cluster_issuer == "my-custom-issuer" + + +# ============================================================================= +# Non-Interactive: validation logic tests +# ============================================================================= + + +class TestNonInteractiveLetsEncryptValidation: + """Validate the non-interactive LE validation block in app.py. + + LE is enabled purely by the presence of mas_le_email. + """ + + def _make_app(self, routing_mode="path", le_email="test@ibm.com", cluster_issuer="", instance_id="testinst"): + app = MagicMock(spec=InstallApp) + app.isInteractiveMode = False + app.params = { + "mas_routing_mode": routing_mode, + "mas_le_email": le_email, + "mas_cluster_issuer": cluster_issuer, + "mas_instance_id": instance_id, + } + app.setParam = lambda key, value: app.params.__setitem__(key, value) + app.getParam = lambda key: app.params.get(key, "") + app.fatalError = MagicMock(side_effect=SystemExit(1)) + return app + + def _run_validation(self, app): + """Replicate the non-interactive LE validation block from app.py.""" + leEmail = app.getParam("mas_le_email") + + if leEmail and app.getParam("mas_routing_mode") != "path": + app.fatalError("Let's Encrypt HTTP-01 Requires Path-Based Routing") + + if leEmail and not app.getParam("mas_cluster_issuer"): + app.setParam("mas_cluster_issuer", f"{app.getParam('mas_instance_id')}-http01-le-prod") + + def test_valid_le_path_routing_sets_cluster_issuer(self): + """Valid config: email + path routing → cluster issuer auto-set.""" + app = self._make_app(routing_mode="path", le_email="test@ibm.com", cluster_issuer="") + self._run_validation(app) + assert app.getParam("mas_cluster_issuer") == "testinst-http01-le-prod" + app.fatalError.assert_not_called() + + def test_valid_le_path_routing_custom_issuer_preserved(self): + """When user provides --mas-cluster-issuer, it is NOT overwritten.""" + app = self._make_app(routing_mode="path", le_email="test@ibm.com", cluster_issuer="my-custom-issuer") + self._run_validation(app) + assert app.getParam("mas_cluster_issuer") == "my-custom-issuer" + app.fatalError.assert_not_called() + + def test_le_with_subdomain_routing_raises_fatal_error(self): + """--le-email + --routing subdomain must trigger a fatal error.""" + app = self._make_app(routing_mode="subdomain", le_email="test@ibm.com") + with pytest.raises(SystemExit): + self._run_validation(app) + app.fatalError.assert_called_once() + assert "Path-Based Routing" in app.fatalError.call_args[0][0] + + def test_no_le_email_no_cluster_issuer_set(self): + """Without --le-email, no cluster issuer is set and no error raised.""" + app = self._make_app(routing_mode="path", le_email="") + self._run_validation(app) + assert app.getParam("mas_cluster_issuer") == "" + app.fatalError.assert_not_called() + + def test_le_cluster_issuer_name_matches_ansible_default(self): + """Issuer name must be -http01-le-prod to match mas_http01_le_prod_issuer_name.""" + app = self._make_app(routing_mode="path", le_email="test@ibm.com", cluster_issuer="", instance_id="myinstance") + self._run_validation(app) + assert app.getParam("mas_cluster_issuer") == "myinstance-http01-le-prod" + + +# ============================================================================= +# Integration: interactive flow via full install harness +# ============================================================================= + + +class TestInteractiveLetsEncryptIntegration: + """Full interactive install flow tests covering the LE prompt.""" + + def _common_handlers(self, tmpdir, le_response): + """Full prompt handler set for a 9.2.x-dev advanced install with path routing. + + Installs IoT + Monitor + Manage so that the system Db2 and Kafka prompts + are exercised — matching the same app mix as test_dev_mode path routing test. + """ + return { + # Cluster connection + ".*Proceed with this cluster?.*": lambda msg: "y", + ".*Show advanced installation options.*": lambda msg: "y", + # Catalog + ".*Select catalog.*": lambda msg: "v9-master-amd64", + ".*Select channel.*": lambda msg: "9.2.x-dev", + # Routing Mode - path + ".*Routing Mode.*": lambda msg: "1", + # Let's Encrypt prompt - controlled by caller + ".*Do you want to use Let.s Encrypt.*": lambda msg: le_response, + # Service Mesh + ".*Enable OpenShift Service Mesh support for MAS.*": lambda msg: "y", + # IngressController config + ".*Configure ingress namespace ownership.*": lambda msg: "y", + # Storage + ".*Use the auto-detected storage classes.*": lambda msg: "y", + # SLS + ".*SLS Mode.*": lambda msg: "1", + ".*SLS channel.*": lambda msg: "1.x-stable", + ".*>License file<.*": lambda msg: f"{tmpdir}/authorized_entitlement.lic", + ".*>Db2 License file<.*": lambda msg: "", + # DRO + ".*DRO.*Namespace.*": lambda msg: "", + ".*Contact e-mail address.*": lambda msg: "maximo@ibm.com", + ".*Contact first name.*": lambda msg: "Test", + ".*Contact last name.*": lambda msg: "Test", + # Credentials + ".*IBM entitlement key.*": lambda msg: "testEntitlementKey", + ".*Artifactory username.*": lambda msg: "testUsername", + ".*Artifactory token.*": lambda msg: "testToken", + # MAS instance + ".*Instance ID.*": lambda msg: "testinst", + ".*Workspace ID.*": lambda msg: "testws", + ".*Workspace.*name.*": lambda msg: "Test Workspace", + ".*Operational Mode.*": lambda msg: "1", + ".*Mas Admin Mode.*": lambda msg: "1", + ".*Certificate issuer kind.*": lambda msg: "2", + ".*Trust default CAs.*": lambda msg: "y", + ".*Cluster ingress certificate secret name.*": lambda msg: "", + ".*Configure domain.*certificate management.*": lambda msg: "n", + ".*Configure SSO properties.*": lambda msg: "n", + ".*Allow special characters.*": lambda msg: "n", + ".*Enable Guided Tour.*": lambda msg: "y", + ".*Enable feature adoption metrics.*": lambda msg: "y", + ".*Enable deployment progression metrics.*": lambda msg: "y", + ".*Enable usability metrics.*": lambda msg: "y", + # Apps — IoT + Monitor + Manage (same mix as dev_mode path routing test) + ".*Install IoT.*": lambda msg: "y", + ".*Custom channel for iot.*": lambda msg: "9.2.x-dev", + ".*Install Monitor.*": lambda msg: "y", + ".*Custom channel for monitor.*": lambda msg: "9.2.x-dev", + ".*Install Manage.*": lambda msg: "y", + ".*Custom channel for manage.*": lambda msg: "9.2.x-dev", + ".*Select a server bundle configuration.*": lambda msg: "1", + ".*Customize database settings.*": lambda msg: "n", + ".*Create demo data.*": lambda msg: "n", + ".*Manage server timezone.*": lambda msg: "GMT", + ".*Base language.*": lambda msg: "EN", + ".*Secondary language.*": lambda msg: "", + ".*Enable integration with Cognos Analytics.*": lambda msg: "n", + ".*Enable integration with Watson Studio Local.*": lambda msg: "n", + ".*Select components to enable.*": lambda msg: "n", + ".*Include customization archive.*": lambda msg: "n", + ".*Install Predict.*": lambda msg: "n", + ".*Install Optimizer.*": lambda msg: "n", + ".*Install Visual Inspection.*": lambda msg: "n", + ".*Install.*Real Estate and Facilities.*": lambda msg: "n", + ".*Install AI Service.*": lambda msg: "n", + ".*Do you want to configure AiCfg.*": lambda msg: "n", + ".*Install Grafana.*": lambda msg: "y", + # MongoDB + ".*MongoDb namespace.*": lambda msg: "mongoce", + ".*Create MongoDb cluster.*": lambda msg: "y", + # Db2 — system Db2 fires because IoT/Monitor are installed + ".*Create system Db2 instance.*": lambda msg: "y", + ".*Re-use System Db2 instance for Manage application.*": lambda msg: "n", + ".*Create Manage dedicated Db2 instance.*": lambda msg: "y", + ".*Select the Manage dedicated DB2 instance type.*": lambda msg: "1", + ".*Install namespace.*": lambda msg: "db2u", + ".*Configure node affinity.*": lambda msg: "n", + ".*Configure node tolerations.*": lambda msg: "n", + ".*Customize CPU and memory request/limit.*": lambda msg: "n", + ".*Customize storage capacity.*": lambda msg: "n", + r".*Select Db2 Custom Resource\(CR\).*": lambda msg: "n", + # Kafka — fires because IoT is installed + ".*Select Kafka provider.*": lambda msg: "1", + ".*Strimzi namespace.*": lambda msg: "strimzi", + ".*Use pod templates.*": lambda msg: "n", + ".*Create system Kafka instance.*": lambda msg: "y", + ".*Kafka version.*": lambda msg: "3.8.0", + # Final + ".*Use additional configurations.*": lambda msg: "n", + ".*Proceed with these settings.*": lambda msg: "y", + } + + def test_interactive_le_enabled_path_routing(self, tmpdir): + """Interactive: select path routing, opt into LE, provide email — LE enabled.""" + handlers = self._common_handlers(tmpdir, le_response="y") + # Insert email prompt after the LE yes answer + handlers[".*Let.s Encrypt e-mail.*"] = lambda msg: "test@ibm.com" + + config = InstallTestConfig( + prompt_handlers=handlers, + current_catalog=None, + architecture="amd64", + is_sno=False, + is_airgap=False, + storage_class_name="nfs-client", + storage_provider="nfs", + storage_provider_name="NFS Client", + ocp_version="4.18.0", + timeout_seconds=60, + argv=["--dev-mode"], + ) + run_install_test(tmpdir, config) + + def test_interactive_le_disabled_path_routing(self, tmpdir): + """Interactive: select path routing, decline LE — normal DNS-01 flow continues.""" + handlers = self._common_handlers(tmpdir, le_response="n") + + config = InstallTestConfig( + prompt_handlers=handlers, + current_catalog=None, + architecture="amd64", + is_sno=False, + is_airgap=False, + storage_class_name="nfs-client", + storage_provider="nfs", + storage_provider_name="NFS Client", + ocp_version="4.18.0", + timeout_seconds=60, + argv=["--dev-mode"], + ) + run_install_test(tmpdir, config) diff --git a/tekton/src/params/install.yml.j2 b/tekton/src/params/install.yml.j2 index caf2ef6cd6d..31b73e7d451 100644 --- a/tekton/src/params/install.yml.j2 +++ b/tekton/src/params/install.yml.j2 @@ -465,6 +465,10 @@ type: string default: "" description: Name of the IngressController to use for path-based routing +- name: mas_le_email + type: string + default: "" + description: E-mail address to register with Let's Encrypt. When set, Let's Encrypt HTTP-01 is automatically enabled (path routing mode only, cluster must be publicly accessible on port 80) - name: mas_trust_default_cas type: string default: "" diff --git a/tekton/src/pipelines/taskdefs/core/suite-dns.yml.j2 b/tekton/src/pipelines/taskdefs/core/suite-dns.yml.j2 index a764e6c05d9..8affef13d05 100644 --- a/tekton/src/pipelines/taskdefs/core/suite-dns.yml.j2 +++ b/tekton/src/pipelines/taskdefs/core/suite-dns.yml.j2 @@ -64,6 +64,14 @@ value: $(params.route53_subdomain) - name: custom_labels value: $(params.custom_labels) + - name: mas_issuer_kind + value: $(params.mas_issuer_kind) + - name: mas_ingress_controller_name + value: $(params.mas_ingress_controller_name) + - name: mas_le_email + value: $(params.mas_le_email) + - name: mas_cluster_issuer + value: $(params.mas_cluster_issuer) taskRef: kind: Task name: mas-devops-suite-dns \ No newline at end of file diff --git a/tekton/src/tasks/suite-dns.yml.j2 b/tekton/src/tasks/suite-dns.yml.j2 index cf0af0ff5ce..3ace17e68e4 100644 --- a/tekton/src/tasks/suite-dns.yml.j2 +++ b/tekton/src/tasks/suite-dns.yml.j2 @@ -103,6 +103,24 @@ spec: description: Optional string parameter, either path or subdomain, defines the network routing mode used for the suite. default: "" + # Certificate issuer kind — ClusterIssuer (cluster mode) or Issuer (namespaced/minimal) + - name: mas_issuer_kind + type: string + default: "" + + # IngressController name used to derive the HTTP-01 solver ingressClassName + - name: mas_ingress_controller_name + type: string + default: "" + + # Let's Encrypt HTTP-01 + - name: mas_le_email + type: string + default: "" + - name: mas_cluster_issuer + type: string + default: "" + stepTemplate: env: {{ lookup('template', task_src_dir ~ '/common/cli-env.yml.j2') | indent(6) }} @@ -165,6 +183,14 @@ spec: value: $(params.mas_manual_cert_mgmt) - name: MAS_ROUTING_MODE value: $(params.mas_routing_mode) + - name: MAS_ISSUER_KIND + value: $(params.mas_issuer_kind) + - name: MAS_INGRESS_CONTROLLER_NAME + value: $(params.mas_ingress_controller_name) + - name: MAS_LE_EMAIL + value: $(params.mas_le_email) + - name: MAS_CLUSTER_ISSUER + value: $(params.mas_cluster_issuer) steps: - name: suite-dns