diff --git a/python/src/mas/cli/install/app.py b/python/src/mas/cli/install/app.py
index a9433f55fa..9c05b0ec41 100644
--- a/python/src/mas/cli/install/app.py
+++ b/python/src/mas/cli/install/app.py
@@ -878,48 +878,69 @@ def _promptForIngressController(self):
@logMethodCall
def configRoutingMode(self):
- if self.showAdvancedOptions and isVersionEqualOrAfter("9.2.0", self.getParam("mas_channel")) and self.getParam("mas_channel") != "9.2.x-feature":
- self.printH1("Configure Routing Mode")
+ if isVersionEqualOrAfter("9.2.0", self.getParam("mas_channel")):
+ # For 9.2.x-feature channel, explicitly set subdomain routing mode
+ if self.getParam("mas_channel") == "9.2.x-feature":
+ if self.getParam("mas_routing_mode") == "":
+ self.setParam("mas_routing_mode", "subdomain")
+ logger.info("Routing mode set to 'subdomain' for 9.2.x-feature channel")
+ return
masDomain = self._getMasDomainForDisplay()
- self.printDescription(
- [
- "Maximo Application Suite can be configured in one of two ways:",
- "",
- " 1. Single domain with path-based routing across the suite",
- f" Example: https://{masDomain}/admin",
- "",
- " 2. Multi domain with subdomain-based routing across the suite",
- f" Example: https://admin.{masDomain}",
- "",
- "Path-based routing requires the IngressController to have the routeAdmission policy",
- "set to 'InterNamespaceAllowed'. This allows routes to claim the same hostname across",
- "different namespaces, which is necessary for path-based routing to function correctly.",
- "",
- "For more information refer to:",
- "https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/ingress_and_load_balancing/routes#nw-route-admission-policy_configuring-routes",
- ]
- )
+ # Determine routing mode based on whether we're in advanced mode
+ if self.showAdvancedOptions:
+ # Advanced mode: Show full prompt and let user choose
+ self.printH1("Configure Routing Mode")
+
+ self.printDescription(
+ [
+ "Maximo Application Suite can be configured in one of two ways:",
+ "",
+ " 1. Single domain with path-based routing across the suite",
+ f" Example: https://{masDomain}/admin",
+ "",
+ " 2. Multi domain with subdomain-based routing across the suite",
+ f" Example: https://admin.{masDomain}",
+ "",
+ "Path-based routing requires the IngressController to have the routeAdmission policy",
+ "set to 'InterNamespaceAllowed'. This allows routes to claim the same hostname across",
+ "different namespaces, which is necessary for path-based routing to function correctly.",
+ "",
+ "For more information refer to:",
+ "https://docs.redhat.com/en/documentation/openshift_container_platform/4.20/html/ingress_and_load_balancing/routes#nw-route-admission-policy_configuring-routes",
+ ]
+ )
- routingModeInt = self.promptForInt("Routing Mode", default=1, min=1, max=2)
- routingModeOptions = ["path", "subdomain"]
- selectedMode = routingModeOptions[routingModeInt - 1]
+ routingModeInt = self.promptForInt("Routing Mode", default=1, min=1, max=2)
+ routingModeOptions = ["path", "subdomain"]
+ selectedMode = routingModeOptions[routingModeInt - 1]
+ else:
+ # Simplified mode: Default to path and inform user
+ selectedMode = "path"
+ self.printDescription(
+ [
+ "",
+ "Routing mode defaulting to 'path' for MAS 9.2+",
+ f" Example: https://{masDomain}/admin",
+ "",
+ "Note: Routing mode selection is available in advanced installation mode.",
+ "For MAS 9.2+ installations, path-based routing is the default if not explicitly configured.",
+ "",
+ ]
+ )
+ # Now validate the selected/defaulted mode
if selectedMode == "path":
canConfigure = self._checkIngressControllerPermissions()
if not canConfigure:
self.printDescription(
[
"",
- "Your cluster ingress currently does not support path-based routing",
- "",
- "If you wish to configure MAS with path-based routing, contact your OpenShift",
- "administrator to apply the following configuration:",
+ "Insufficient permissions to validate IngressController configuration",
"",
- " spec:",
- " routeAdmission:",
- " namespaceOwnership: InterNamespaceAllowed",
+ "Path-based routing requires IngressController permissions to validate the configuration.",
+ "Contact your OpenShift administrator to grant the necessary permissions.",
"",
"MAS will be configured to use subdomain-based routing.",
]
@@ -937,36 +958,34 @@ def configRoutingMode(self):
self.setParam("mas_routing_mode", "path")
self.printDescription([f"IngressController '{selectedController}' is configured for path-based routing."])
else:
- self.printDescription(
- [
- "",
- "Your cluster ingress currently does not support path-based routing",
- "",
- "The following setting needs to be applied to the IngressController:",
- "",
- " spec:",
- " routeAdmission:",
- " namespaceOwnership: InterNamespaceAllowed",
- "",
- ]
- )
-
- if self.yesOrNo("Configure ingress namespace ownership policy to enable path-based routing for MAS"):
+ if self._promptForIngressConfiguration(selectedController):
self.setParam("mas_routing_mode", "path")
self.setParam("mas_configure_ingress", "true")
self.printDescription(
[f"IngressController '{selectedController}' will be configured before MAS installation begins."]
)
else:
- self.printDescription(
- [
- "",
- "Path-based routing requires IngressController configuration.",
- "MAS will be configured to use subdomain-based routing.",
- ]
+ self.fatalError(
+ "\n".join(
+ [
+ "IngressController Configuration Required",
+ "",
+ "========================================================================",
+ "Path-based routing requires IngressController configuration.",
+ "",
+ "To proceed, you have the following options:",
+ "",
+ "1. Re-run the installation and agree to configure the IngressController",
+ "",
+ "2. Manually configure it before installation by running:",
+ f" oc patch ingresscontroller {selectedController} -n openshift-ingress-operator \\",
+ " --type=merge \\",
+ ' --patch=\'{"spec":{"routeAdmission":{"namespaceOwnership":"InterNamespaceAllowed"}}}\'',
+ "",
+ "3. Use subdomain routing mode instead path",
+ ]
+ )
)
- self.setParam("mas_routing_mode", "subdomain")
- self.setParam("mas_ingress_controller_name", "")
else:
self.setParam("mas_routing_mode", "subdomain")
@@ -1014,6 +1033,179 @@ def _checkIngressControllerPermissions(self, controllerName="default"):
logger.warning(f"User may not have permissions to configure IngressController '{controllerName}': {e}")
return False
+ def _promptForIngressConfiguration(self, ingressControllerName):
+ """
+ Display IngressController configuration prompt and handle user response.
+
+ Args:
+ ingressControllerName: Name of the IngressController
+
+ Returns:
+ bool: True if user wants to configure, False otherwise
+ """
+ self.printDescription(
+ [
+ "",
+ "IngressController is not configured for path-based routing",
+ "",
+ f"IngressController '{ingressControllerName}' exists but is not properly configured",
+ "for path-based routing.",
+ "",
+ "The following setting needs to be applied to the IngressController:",
+ "",
+ " spec:",
+ " routeAdmission:",
+ " namespaceOwnership: InterNamespaceAllowed",
+ "",
+ ]
+ )
+
+ return self.yesOrNo("Configure ingress namespace ownership policy to enable path-based routing for MAS")
+
+ def _validateAndConfigureIngressControllerForPathRouting(self):
+ """
+ Validate and configure IngressController for path-based routing in non-interactive mode.
+
+ This function:
+ 1. Determines the IngressController name (from CLI flag, parameter, or default)
+ 2. Checks user permissions
+ 3. Validates IngressController existence and configuration
+ 4. Prompts user to configure if needed (respects --no-confirm flag)
+ 5. Sets mas_configure_ingress parameter if user agrees
+
+ Raises:
+ SystemExit: If validation fails or user declines configuration
+ """
+ # Determine IngressController name
+ ingressControllerName = None
+ if hasattr(self.args, "mas_ingress_controller_name") and self.args.mas_ingress_controller_name:
+ ingressControllerName = self.args.mas_ingress_controller_name
+ logger.info(f"Using IngressController '{ingressControllerName}' from CLI flag")
+ elif self.getParam("mas_ingress_controller_name"):
+ ingressControllerName = self.getParam("mas_ingress_controller_name")
+ logger.info(f"Using IngressController '{ingressControllerName}' from existing parameter")
+ else:
+ ingressControllerName = "default"
+ logger.info("No IngressController specified, defaulting to 'default'")
+
+ self.setParam("mas_ingress_controller_name", ingressControllerName)
+
+ # Check permissions BEFORE attempting to check the IngressController
+ canConfigure = self._checkIngressControllerPermissions()
+ if not canConfigure:
+ self.fatalError(
+ "\n".join(
+ [
+ "Insufficient Permissions to Validate IngressController Configuration",
+ "========================================================================",
+ "You do not have sufficient permissions to validate the IngressController",
+ f"configuration for path-based routing (IngressController: '{ingressControllerName}').",
+ "",
+ "Path-based routing requires IngressController permissions to validate the configuration.",
+ "Contact your OpenShift administrator to grant the necessary permissions.",
+ "",
+ "Alternatively, you can use subdomain routing mode:",
+ " mas install --routing subdomain ...",
+ ]
+ )
+ )
+
+ exists, isConfigured = self._checkIngressControllerForPathRouting(ingressControllerName)
+
+ if not exists:
+ self.fatalError(
+ "\n".join(
+ [
+ "IngressController Not Found",
+ "",
+ "========================================================================",
+ f"You selected IngressController '{ingressControllerName}', but it does not exist",
+ "in the openshift-ingress-operator namespace.",
+ "",
+ "To fix this issue:",
+ "",
+ "1. List available IngressControllers:",
+ " oc get ingresscontroller -n openshift-ingress-operator",
+ "",
+ "2. Use an existing controller name with --ingress-controller-name flag:",
+ " mas install --routing path --ingress-controller-name [existing-controller] ...",
+ "",
+ "3. Or use the default controller (usually named 'default'):",
+ " mas install --routing path --ingress-controller-name default ...",
+ "",
+ "Alternatively, you can use subdomain routing mode:",
+ " mas install --routing subdomain ...",
+ ]
+ )
+ )
+ elif not isConfigured:
+ if hasattr(self.args, "mas_configure_ingress") and self.args.mas_configure_ingress:
+ logger.info(f"IngressController '{ingressControllerName}' will be configured for path-based routing before MAS installation")
+ self.setParam("mas_configure_ingress", "true")
+ else:
+ # If --no-confirm is NOT used, prompt user to configure ingress
+ if not self.noConfirm:
+ if self._promptForIngressConfiguration(ingressControllerName):
+ self.setParam("mas_configure_ingress", "true")
+ logger.info(f"IngressController '{ingressControllerName}' will be configured for path-based routing before MAS installation")
+ else:
+ self.fatalError(
+ "\n".join(
+ [
+ "IngressController Configuration Required",
+ "",
+ "========================================================================",
+ "Path-based routing requires IngressController configuration.",
+ "",
+ "To proceed, you have the following options:",
+ "",
+ "1. Add the --configure-ingress flag to configure it during installation:",
+ f" mas install --routing path --ingress-controller-name {ingressControllerName} --configure-ingress ...",
+ "",
+ "2. Manually configure it before installation by running:",
+ f" oc patch ingresscontroller {ingressControllerName} -n openshift-ingress-operator \\",
+ " --type=merge \\",
+ ' --patch=\'{"spec":{"routeAdmission":{"namespaceOwnership":"InterNamespaceAllowed"}}}\'',
+ "",
+ "3. Use subdomain routing mode instead:",
+ " mas install --routing subdomain ...",
+ ]
+ )
+ )
+ else:
+ # --no-confirm is used, fail immediately with instructions
+ self.fatalError(
+ "\n".join(
+ [
+ "IngressController Not Configured for Path-Based Routing",
+ "",
+ "========================================================================",
+ f"IngressController '{ingressControllerName}' exists but is not properly configured",
+ "for path-based routing.",
+ "",
+ "Required Configuration:",
+ " spec:",
+ " routeAdmission:",
+ " namespaceOwnership: InterNamespaceAllowed",
+ "",
+ "To fix this issue, you have two options:",
+ "",
+ "1. Add the --configure-ingress flag to configure it during installation:",
+ f" mas install --routing path --ingress-controller-name {ingressControllerName} --configure-ingress --no-confirm ...",
+ "",
+ "2. Manually configure it before installation by running:",
+ f" oc patch ingresscontroller {ingressControllerName} -n openshift-ingress-operator \\",
+ " --type=merge \\",
+ ' --patch=\'{"spec":{"routeAdmission":{"namespaceOwnership":"InterNamespaceAllowed"}}}\'',
+ "",
+ "Alternatively, you can use subdomain routing mode:",
+ " mas install --routing subdomain ...",
+ ]
+ )
+ )
+ else:
+ logger.info(f"IngressController '{ingressControllerName}' is already configured for path-based routing")
+
@logMethodCall
def configManualRoutesMgmt(self) -> None:
if self.showAdvancedOptions:
@@ -2389,6 +2581,21 @@ def nonInteractiveMode(self) -> None:
if self.getParam("mas_issuer_kind") == "":
self.setParam("mas_issuer_kind", "ClusterIssuer")
+ # Set default routing mode for MAS 9.2+ in non-interactive mode if not explicitly configured
+ if isVersionEqualOrAfter("9.2.0", self.getParam("mas_channel")):
+ if self.getParam("mas_routing_mode") == "":
+ # For 9.2.x-feature channel, default to subdomain routing
+ if self.getParam("mas_channel") == "9.2.x-feature":
+ self.setParam("mas_routing_mode", "subdomain")
+ logger.info("Routing mode defaulting to 'subdomain' for 9.2.x-feature channel in non-interactive mode")
+ else:
+ # For other 9.2+ channels, default to path routing
+ self.setParam("mas_routing_mode", "path")
+ logger.info("Routing mode defaulting to 'path' for MAS 9.2+ in non-interactive mode")
+ # Set default ingress controller for path-based routing if not already set
+ if self.getParam("mas_ingress_controller_name") == "":
+ self.setParam("mas_ingress_controller_name", "default")
+
self.evaluatePreInstallRBACAccess()
self.setDB2DefaultChannel()
@@ -2565,109 +2772,7 @@ def install(self, argv):
# Validate IngressController configuration for path-based routing (non-interactive mode only)
if not self.isInteractiveMode and self.getParam("mas_routing_mode") == "path":
- ingressControllerName = None
- if hasattr(self.args, "mas_ingress_controller_name") and self.args.mas_ingress_controller_name:
- ingressControllerName = self.args.mas_ingress_controller_name
- logger.info(f"Using IngressController '{ingressControllerName}' from CLI flag")
- elif self.getParam("mas_ingress_controller_name"):
- ingressControllerName = self.getParam("mas_ingress_controller_name")
- logger.info(f"Using IngressController '{ingressControllerName}' from existing parameter")
- else:
- ingressControllerName = "default"
- logger.info("No IngressController specified, defaulting to 'default'")
-
- self.setParam("mas_ingress_controller_name", ingressControllerName)
-
- # Check permissions BEFORE attempting to check the IngressController
- canConfigure = self._checkIngressControllerPermissions()
- if not canConfigure:
-
- self.fatalError(
- "\n".join(
- [
- "IngressController Configuration Requires Administrator Permissions",
- "========================================================================",
- "You do not have sufficient permissions to check or configure the",
- f"IngressController '{ingressControllerName}'.",
- "",
- "If you wish to configure MAS with path-based routing, contact your OpenShift",
- "administrator to apply the following configuration:",
- "",
- " spec:",
- " routeAdmission:",
- " namespaceOwnership: InterNamespaceAllowed",
- "",
- "Alternatively, you can use subdomain routing mode:",
- " mas install --routing subdomain ...",
- ]
- )
- )
-
- exists, isConfigured = self._checkIngressControllerForPathRouting(ingressControllerName)
-
- if not exists:
- self.fatalError(
- "\n".join(
- [
- "IngressController Not Found",
- "",
- "========================================================================",
- f"You selected IngressController '{ingressControllerName}', but it does not exist",
- "in the openshift-ingress-operator namespace.",
- "",
- "To fix this issue:",
- "",
- "1. List available IngressControllers:",
- " oc get ingresscontroller -n openshift-ingress-operator",
- "",
- "2. Use an existing controller name with --ingress-controller-name flag:",
- " mas install --routing path --ingress-controller-name [existing-controller] ...",
- "",
- "3. Or use the default controller (usually named 'default'):",
- " mas install --routing path --ingress-controller-name default ...",
- "",
- "Alternatively, you can use subdomain routing mode:",
- " mas install --routing subdomain ...",
- ]
- )
- )
- elif not isConfigured:
- if hasattr(self.args, "mas_configure_ingress") and self.args.mas_configure_ingress:
- logger.info(f"IngressController '{ingressControllerName}' will be configured for path-based routing before MAS installation")
- self.setParam("mas_configure_ingress", "true")
- else:
- self.fatalError(
- "\n".join(
- [
- "IngressController Not Configured for Path-Based Routing",
- "",
- "========================================================================",
- f"IngressController '{ingressControllerName}' exists but is not properly configured",
- "for path-based routing.",
- "",
- "Required Configuration:",
- " spec:",
- " routeAdmission:",
- " namespaceOwnership: InterNamespaceAllowed",
- "",
- "To fix this issue, you have two options:",
- "",
- "1. Add the --configure-ingress flag to configure it during installation:",
- f" (Optionally, you can provide your custom IngressController name instead of {ingressControllerName} )",
- f" mas install --routing path --ingress-controller-name {ingressControllerName} --configure-ingress ...",
- "",
- "2. Manually configure it before installation by running:",
- f" oc patch ingresscontroller {ingressControllerName} -n openshift-ingress-operator \\",
- " --type=merge \\",
- ' --patch=\'{"spec":{"routeAdmission":{"namespaceOwnership":"InterNamespaceAllowed"}}}\'',
- "",
- "Alternatively, you can use subdomain routing mode:",
- " mas install --routing subdomain ...",
- ]
- )
- )
- else:
- logger.info(f"IngressController '{ingressControllerName}' is already configured for path-based routing")
+ self._validateAndConfigureIngressControllerForPathRouting()
# Based on the parameters set the annotations correctly
self.configAnnotations()
diff --git a/python/test/install/test_dev_mode.py b/python/test/install/test_dev_mode.py
index 072c815391..2801912336 100644
--- a/python/test/install/test_dev_mode.py
+++ b/python/test/install/test_dev_mode.py
@@ -409,6 +409,11 @@ def test_install_master_dev_mode_non_interactive(tmpdir):
"MAS_SUPERUSER_PASSWORD",
"--mas-channel",
"9.2.x-dev",
+ "--routing",
+ "path",
+ "--ingress-controller-name",
+ "default",
+ "--configure-ingress",
"--iot-channel",
"9.2.x-dev",
"--db2-system",
@@ -690,6 +695,11 @@ def test_install_master_dev_mode_non_interactive_with_slack(tmpdir):
"MAS_SUPERUSER_PASSWORD",
"--mas-channel",
"9.2.x-dev",
+ "--routing",
+ "path",
+ "--ingress-controller-name",
+ "default",
+ "--configure-ingress",
"--iot-channel",
"9.2.x-dev",
"--db2-system",
diff --git a/python/test/install/test_routing_mode.py b/python/test/install/test_routing_mode.py
index 39b22365a7..2567872060 100644
--- a/python/test/install/test_routing_mode.py
+++ b/python/test/install/test_routing_mode.py
@@ -50,8 +50,10 @@ def create_mock_app():
app._checkIngressControllerPermissions = InstallApp._checkIngressControllerPermissions.__get__(app, InstallApp)
app._checkIngressControllerForPathRouting = InstallApp._checkIngressControllerForPathRouting.__get__(app, InstallApp)
app._promptForIngressController = InstallApp._promptForIngressController.__get__(app, InstallApp)
+ app._promptForIngressConfiguration = InstallApp._promptForIngressConfiguration.__get__(app, InstallApp)
app._getMasDomainForDisplay = InstallApp._getMasDomainForDisplay.__get__(app, InstallApp)
app.configRoutingMode = InstallApp.configRoutingMode.__get__(app, InstallApp)
+ app.fatalError = MagicMock(side_effect=SystemExit(1))
return app
@@ -183,16 +185,17 @@ def get_side_effect(api_version, kind):
assert app.getParam("mas_ingress_controller_name") == "default"
assert app.getParam("mas_configure_ingress") == "true"
- def test_interactive_path_routing_user_declines_falls_back_to_subdomain(self):
- """Test fallback to subdomain when user declines to configure IngressController."""
+ def test_interactive_path_routing_user_declines_fails_with_error(self):
+ """Test that installation fails with error when user declines to configure IngressController."""
app = create_mock_app()
app.isInteractiveMode = True
app.showAdvancedOptions = True
app.setParam("mas_channel", "9.2.0")
app.setParam("mas_instance_id", "test-inst")
- # User declines to configure ingress
- app.yesOrNo.return_value = False
+ # User selects path mode (option 1) and declines to configure ingress
+ app.promptForInt.return_value = 1 # Select path mode
+ app.yesOrNo.return_value = False # Decline configuration
# Mock IngressController not configured
controller = create_ingress_controller_mock(namespace_ownership="Strict")
@@ -223,11 +226,9 @@ def get_side_effect(api_version, kind):
# Mock isVersionEqualOrAfter to return True for version check
with patch("mas.cli.install.app.isVersionEqualOrAfter", return_value=True):
- app.configRoutingMode()
-
- # Verify fallback to subdomain
- assert app.getParam("mas_routing_mode") == "subdomain"
- assert app.getParam("mas_ingress_controller_name") == ""
+ # Should raise SystemExit when user declines configuration
+ with pytest.raises(SystemExit):
+ app.configRoutingMode()
def test_interactive_path_routing_patch_fails_gracefully(self):
"""Test graceful failure when IngressController patch operation fails."""
@@ -879,6 +880,8 @@ def get_side_effect(api_version, kind):
with patch("mas.cli.install.app.isVersionEqualOrAfter", return_value=True):
# Set up promptForInt to return different values for routing mode and controller selection
app.promptForInt.side_effect = [1, 1] # Path mode, first controller
+ # User agrees to configure IngressController
+ app.yesOrNo.return_value = True
app.configRoutingMode()
# Verify all parameters are set correctly
@@ -988,8 +991,8 @@ def test_complete_end_to_end_flow_advanced_options_disabled_skips_routing(self):
app.configRoutingMode()
# Routing mode should not be set (method should exit early)
- # Default is subdomain if not explicitly set
- assert app.getParam("mas_routing_mode") == ""
+ # Default is path if not explicitly set for 9.2+
+ assert app.getParam("mas_routing_mode") == "path"
# promptForInt should not be called (no routing mode prompt)
assert app.promptForInt.call_count == 0