Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"files": "build/bin/config/oscap/ssg-rhel9-ds.xml|^.secrets.baseline$|^docs/catalogs/",
"lines": null
},
"generated_at": "2026-07-24T11:52:31Z",
"generated_at": "2026-07-31T14:03:18Z",
"plugins_used": [
{
"name": "AWSKeyDetector"
Expand Down Expand Up @@ -270,7 +270,7 @@
"hashed_secret": "b2817467154949a61f8e9ad31d1eeaf03221cbfa",
"is_secret": false,
"is_verified": false,
"line_number": 387,
"line_number": 392,
"type": "Secret Keyword",
"verified_result": null
}
Expand Down
18 changes: 17 additions & 1 deletion image/cli/mascli/functions/gitops_aiservice_tenant
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ ibm_aiservice_tenant(required):
--aiservice-watsonxai-url ${COLOR_YELLOW}AISERVICE_WATSONXAI_URL${TEXT_RESET} Endpoint URL for Watsonx.ai
--aiservice-watsonx-full ${COLOR_YELLOW}AISERVICE_WATSONX_FULL${TEXT_RESET} Full URL for Watsonx.ai including API key
--tenant-scheduling-config-file ${COLOR_YELLOW}TENANT_SCHEDULING_CONFIG_FILE${TEXT_RESET} Path to tenant scheduling configuration file (tolerations and nodeSelector)
--enable-deprovisioning ${COLOR_YELLOW}AISERVICE_ENABLE_DEPROVISIONING${TEXT_RESET} When true, the tenant is deprovisioned when its AIServiceTenant CR is deleted
--tenant-operator-config-file ${COLOR_YELLOW}TENANT_OPERATOR_CONFIG_FILE${TEXT_RESET} Path to tenant operator configuration file
--enable-deprovisioning ${COLOR_YELLOW}AISERVICE_ENABLE_DEPROVISIONING${TEXT_RESET} When true, the tenant is deprovisioned when its AIServiceTenant CR is deleted

AiService :
--aiservice-namespace ${COLOR_YELLOW}AISERVICE_NAMESPACE${TEXT_RESET} The namespace where AI Service is deployed
Expand Down Expand Up @@ -270,6 +271,10 @@ function gitops_aiservice_tenant_noninteractive() {
--tenant-scheduling-config-yaml)
export TENANT_SCHEDULING_CONFIG_YAML=$1 && shift
;;

--tenant-operator-config-file)
export TENANT_OPERATOR_CONFIG_FILE=$1 && shift
;;

--enable-deprovisioning)
export AISERVICE_ENABLE_DEPROVISIONING=$1 && shift
Expand Down Expand Up @@ -424,6 +429,12 @@ else
echo_reset_dim "Scheduling constraints (tolerations/nodeSelector) .............. ${COLOR_MAGENTA}Not Configured"
fi

if [[ -n "$TENANT_OPERATOR_CONFIG_FILE" ]]; then
echo_reset_dim "Tenant operator configuration file .............. ${COLOR_MAGENTA}${TENANT_OPERATOR_CONFIG_FILE}"
else
echo_reset_dim "Tenant operator configuration file .............. ${COLOR_MAGENTA}Not set"
fi

echo_reset_dim "MAS ICR CP registry ............................................ ${COLOR_MAGENTA}${MAS_ICR_CP}"
echo_reset_dim "MAS ICR CPOPEN registry ........................................ ${COLOR_MAGENTA}${MAS_ICR_CPOPEN}"

Expand Down Expand Up @@ -517,6 +528,11 @@ fi
export TENANT_SCHEDULING_CONFIG=$(yq eval '.' ${TENANT_SCHEDULING_CONFIG_YAML})
echo -e "\n - TENANT_SCHEDULING_CONFIG content .................. ${TENANT_SCHEDULING_CONFIG}"
fi

if [[ -n "$TENANT_OPERATOR_CONFIG_FILE" && -s "$TENANT_OPERATOR_CONFIG_FILE" ]]; then
Comment thread
jasmin-30 marked this conversation as resolved.
export TENANT_OPERATOR_CONFIG=$(yq eval '.' ${TENANT_OPERATOR_CONFIG_FILE})
echo -e "\n - TENANT_OPERATOR_CONFIG .................. ${TENANT_OPERATOR_CONFIG}"
fi

# finally push them into the git repo

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,8 @@ ibm_aiservice_tenant:
tenant_scheduling_config:
{{ TENANT_SCHEDULING_CONFIG | indent(4)}}
{% endif %}

{% if TENANT_OPERATOR_CONFIG is defined and TENANT_OPERATOR_CONFIG != '' %}
tenant_operator_config:
{{ TENANT_OPERATOR_CONFIG | indent(4)}}
{% endif %}
7 changes: 7 additions & 0 deletions python/src/mas/cli/aiservice/install/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ def nonInteractiveMode(self) -> None:
self.db2LicenseFileLocal = None

self.aiserviceTenantSchedulingConfigFileLocal = None
self.aiserviceTenantOperatorConfigFileLocal = None

self.approvals = {
"approval_aiservice": {"id": "aiservice"},
Expand Down Expand Up @@ -278,6 +279,10 @@ def nonInteractiveMode(self) -> None:
if value is not None and value != "":
self.aiserviceTenantSchedulingConfigFileLocal = value

elif key == "tenant_operator_config_file":
if value is not None and value != "":
self.aiserviceTenantOperatorConfigFileLocal = value

elif key == "db2_action_aiservice":
# Check if external DB parameters are provided
externalDbParams = ["aiservice_db_jdbc_url", "aiservice_db_username", "aiservice_db_password"]
Expand Down Expand Up @@ -718,7 +723,9 @@ def aiServiceTenantSettings(self) -> None:
self.promptForString("Entitlement end date (YYYY-MM-DD)", "tenant_entitlement_end_date", default=oneyear.strftime("%Y-%m-%d"))

self.aiserviceTenantSchedulingConfigFileLocal = None
self.aiserviceTenantOperatorConfigFileLocal = None
self.configSchedulingConstraints()
self.configTenantOperator()

@logMethodCall
def configSchedulingConstraints(self):
Expand Down
2 changes: 2 additions & 0 deletions python/src/mas/cli/aiservice/install/argBuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ def buildCommand(self) -> str:
command += f" --tenant-entitlement-end-date \"{self.getParam('tenant_entitlement_end_date')}\"{newline}"
if self.aiserviceTenantSchedulingConfigFileLocal:
command += f' --tenant-scheduling-config-file "{self.aiserviceTenantSchedulingConfigFileLocal}"{newline}'
if self.aiserviceTenantOperatorConfigFileLocal:
command += f' --tenant-operator-config-file "{self.aiserviceTenantOperatorConfigFileLocal}"{newline}'

if self.getParam("rsl_ca_crt") != "":
command += f" --rsl-ca-crt \"{self.getParam('rsl_ca_crt')}\"{newline}"
Expand Down
7 changes: 7 additions & 0 deletions python/src/mas/cli/aiservice/install/argParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,13 @@ def isValidFile(parser, arg) -> str:
help="Path to the YAML file that contains the scheduling configuration for tenant",
type=lambda x: isValidFile(aiServiceinstallArgParser, x),
)
aiserviceAdvancedArgGroup.add_argument(
"--tenant-operator-config-file",
dest="tenant_operator_config_file",
required=False,
help="Path to the YAML file that contains the tenant operator customization settings",
type=lambda x: isValidFile(aiServiceinstallArgParser, x),
)


# Database Configuration
Expand Down
3 changes: 3 additions & 0 deletions python/src/mas/cli/aiservice/install/summarizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ def aiServiceSummary(self) -> None:
if self.aiserviceTenantSchedulingConfigFileLocal:
self.printSummary("Scheduling configuration file", self.aiserviceTenantSchedulingConfigFileLocal)

if self.aiserviceTenantOperatorConfigFileLocal:
self.printSummary("Tenant operator configuration file", self.aiserviceTenantOperatorConfigFileLocal)

self.printH2("S3 Configuration")
# self.printParamSummary("Storage provider", "aiservice_s3_provider")
if self.getParam("minio_root_user") is not None and self.getParam("minio_root_user") != "":
Expand Down
9 changes: 9 additions & 0 deletions python/src/mas/cli/install/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -1879,7 +1879,9 @@ def aiServiceTenantSettings(self) -> None:
)

self.aiserviceTenantSchedulingConfigFileLocal = None
self.aiserviceTenantOperatorConfigFileLocal = None
self.configSchedulingConstraints()
self.configTenantOperator()

@logMethodCall
def configSchedulingConstraints(self):
Expand Down Expand Up @@ -2005,6 +2007,7 @@ def chooseInstallFlavour(self) -> None:
" - Choose alternative Apache Kafka providers (default to Strimzi)",
" - Customize Grafana storage settings",
" - Customize Scheduling configuration for AI workloads(Training pipeline & Inference services) for AI Service tenant",
" - Customize AI Service tenant operator configuration",
]
)
self.showAdvancedOptions = self.yesOrNo("Show advanced installation options")
Expand All @@ -2018,6 +2021,7 @@ def interactiveMode(self, simplified: bool, advanced: bool) -> None:
self.slsLicenseFileLocal = None
self.db2LicenseFileLocal = None
self.aiserviceTenantSchedulingConfigFileLocal = None
self.aiserviceTenantOperatorConfigFileLocal = None
self.facilitiesPropertiesFileLocal = None

if simplified:
Expand Down Expand Up @@ -2111,6 +2115,7 @@ def nonInteractiveMode(self) -> None:
self.slsLicenseFileLocal = None
self.db2LicenseFileLocal = None
self.aiserviceTenantSchedulingConfigFileLocal = None
self.aiserviceTenantOperatorConfigFileLocal = None
self.facilitiesPropertiesFileLocal = None

self.approvals: Dict[str, Dict[str, Any]] = {
Expand Down Expand Up @@ -2466,6 +2471,10 @@ def nonInteractiveMode(self) -> None:
if value is not None and value != "":
self.aiserviceTenantSchedulingConfigFileLocal = value

elif key == "tenant_operator_config_file":
if value is not None and value != "":
self.aiserviceTenantOperatorConfigFileLocal = value

# Fail if there's any arguments we don't know how to handle
else:
print(f"Unknown option: {key} {value}")
Expand Down
2 changes: 2 additions & 0 deletions python/src/mas/cli/install/argBuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,8 @@ def buildCommand(self) -> str:
command += f" --tenant-entitlement-end-date \"{self.getParam('tenant_entitlement_end_date')}\"{newline}"
if self.aiserviceTenantSchedulingConfigFileLocal:
command += f' --tenant-scheduling-config-file "{self.aiserviceTenantSchedulingConfigFileLocal}"{newline}'
if self.aiserviceTenantOperatorConfigFileLocal:
command += f' --tenant-operator-config-file "{self.aiserviceTenantOperatorConfigFileLocal}"{newline}'

if self.getParam("rsl_ca_crt") != "":
command += f" --rsl-ca-crt \"{self.getParam('rsl_ca_crt')}\"{newline}"
Expand Down
7 changes: 7 additions & 0 deletions python/src/mas/cli/install/argParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -1104,6 +1104,13 @@ def isValidFile(parser: argparse.ArgumentParser, arg: str) -> str:
help="Path to the YAML file that contains the scheduling configuration for tenant",
type=lambda x: isValidFile(installArgParser, x),
)
aiServiceArgGroup.add_argument(
"--tenant-operator-config-file",
dest="tenant_operator_config_file",
required=False,
help="Path to the YAML file that contains the tenant operator customization settings",
type=lambda x: isValidFile(installArgParser, x),
)

# IBM Cloud Pak for Data
# -----------------------------------------------------------------------------
Expand Down
7 changes: 6 additions & 1 deletion python/src/mas/cli/install/settings/additionalConfigs.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class AdditionalConfigsMixin:
manualCertsDir: str | None
showAdvancedOptions: bool
aiserviceTenantSchedulingConfigFileLocal: str | None
aiserviceTenantOperatorConfigFileLocal: str | None
additionalConfigsSecret: Dict[str, Any] | None
podTemplatesSecret: Dict[str, Any] | None
slsLicenseFileSecret: Dict[str, Any] | None
Expand Down Expand Up @@ -211,12 +212,16 @@ def slsLicenseFile(self) -> None:

def aiserviceConfig(self) -> None:
self.aiserviceConfigSecret = None
aiserviceConfigSecret: dict[str, Any] = {"apiVersion": "v1", "kind": "Secret", "type": "Opaque", "metadata": {"name": "pipeline-aiservice-config"}}

if self.aiserviceTenantSchedulingConfigFileLocal:
aiserviceConfigSecret: dict[str, Any] = {"apiVersion": "v1", "kind": "Secret", "type": "Opaque", "metadata": {"name": "pipeline-aiservice-config"}}
self.setParam("tenant_scheduling_config_file", f"/workspace/aiservice/{path.basename(self.aiserviceTenantSchedulingConfigFileLocal)}")
self.aiserviceConfigSecret = self.addFilesToSecret(aiserviceConfigSecret, self.aiserviceTenantSchedulingConfigFileLocal, "yaml")

if self.aiserviceTenantOperatorConfigFileLocal:
self.setParam("tenant_operator_config_file", f"/workspace/aiservice/{path.basename(self.aiserviceTenantOperatorConfigFileLocal)}")
self.aiserviceConfigSecret = self.addFilesToSecret(aiserviceConfigSecret, self.aiserviceTenantOperatorConfigFileLocal, "yaml")

def db2LicenseFile(self) -> None:
if self.db2LicenseFileLocal:
db2LicenseFileSecret = {"apiVersion": "v1", "kind": "Secret", "type": "Opaque", "metadata": {"name": "pipeline-db2-license"}}
Expand Down
22 changes: 22 additions & 0 deletions python/src/mas/cli/install/settings/aiSettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from os import path
from typing import TYPE_CHECKING, Dict, List, Any
from prompt_toolkit import print_formatted_text
from mas.cli.cli import logMethodCall

if TYPE_CHECKING:
# Type hints for methods and attributes provided by other mixins
Expand All @@ -29,6 +30,7 @@ class AiSettingsMixin:
templatesDir: str
dynamicClient: Any
installAIService: bool
aiserviceTenantOperatorConfigFileLocal: str | None

# Methods from BaseApp
def setParam(self, param: str, value: str) -> None: ...
Expand All @@ -55,6 +57,8 @@ def promptForString(
completer: WordCompleter | None = None,
) -> str: ...

def promptForFile(self, message: str, mustExist: bool = True, default: str = "", envVar: str = "") -> str: ...

def promptForListSelect(self, message: str, options: List[str], param: str | None = None, default: int | None = None) -> str: ...

# Methods from ConfigGeneratorMixin or InstallSettingsMixin
Expand Down Expand Up @@ -142,3 +146,21 @@ def configAi(self) -> None:
else:
self.setParam("configure_aiassistant", "none")
print_formatted_text("AiCfg configuration skipped")

@logMethodCall
def configTenantOperator(self):
if self.showAdvancedOptions:
self.printH1("Tenant operator configuration customization")
self.printDescription(
content=[
"The AI Service tenant operator deployment may be customized, for example, to adjust resource requests/limits",
"To customize the operator, create a YAML configuration file",
"The YAML file may contain any valid configuration for the 'spec.config' field in an OLM Subscription resource",
]
)

configTenantOperator = self.yesOrNo("Customize the AI Service tenant operator deployment")
if configTenantOperator:
self.aiserviceTenantOperatorConfigFileLocal = self.promptForFile(
"Tenant operator configuration YAML file", mustExist=True, envVar="AISERVICE_TENANT_OPERATOR_CONFIG_FILE"
)
6 changes: 6 additions & 0 deletions python/src/mas/cli/install/summarizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class InstallSummarizerMixin:
localConfigDir: str | None
slsLicenseFileLocal: str | None
aiserviceTenantSchedulingConfigFileLocal: str | None
aiserviceTenantOperatorConfigFileLocal: str | None
facilitiesPropertiesFileLocal: str | None
deployCP4D: bool
installAssist: bool
Expand Down Expand Up @@ -413,6 +414,11 @@ def aiServiceSummary(self) -> None:
"Scheduling configuration file",
self.aiserviceTenantSchedulingConfigFileLocal,
)
if self.aiserviceTenantOperatorConfigFileLocal:
self.printSummary(
"Tenant operator configuration file",
self.aiserviceTenantOperatorConfigFileLocal,
)

self.printH2("S3 Configuration")
# self.printParamSummary("Storage provider", "aiservice_s3_provider")
Expand Down
8 changes: 8 additions & 0 deletions python/tests/integration/aiservice_install/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
def test_install_noninteractive(tmpdir):
tmpdir.join("authorized_entitlement.lic").write("testLicense")
tmpdir.join("aiservice-tenant-affinity-config.yaml").write("#")
tmpdir.join("aiservice-tenant-operator-config.yaml").write("#")
with mock.patch("mas.cli.cli.config"):
dynamic_client = MagicMock(DynamicClient)
resources = MagicMock()
Expand Down Expand Up @@ -154,6 +155,8 @@ def test_install_noninteractive(tmpdir):
"2026-08-28",
"--tenant-scheduling-config-file",
f"{tmpdir}/aiservice-tenant-affinity-config.yaml",
"--tenant-operator-config-file",
f"{tmpdir}/aiservice-tenant-operator-config.yaml",
"--rsl-ca-crt",
"testRslCaCert",
"--accept-license",
Expand All @@ -166,6 +169,7 @@ def test_install_noninteractive(tmpdir):
def test_install_interactive_advanced(tmpdir):
tmpdir.join("authorized_entitlement.lic").write("testLicense")
tmpdir.join("aiservice-tenant-affinity-config.yaml").write("#")
tmpdir.join("aiservice-tenant-operator-config.yaml").write("#")
tmpdir.join("mongodb-system.yaml").write("#")
tmpdir.join("cert.crt").write("#")
with mock.patch("mas.cli.cli.config"):
Expand Down Expand Up @@ -259,6 +263,10 @@ def set_mixin_prompt_input(**kwargs):
return "y"
if re.match(".*Scheduling configuration YAML file.*", message):
return f"{tmpdir}/aiservice-tenant-affinity-config.yaml"
if re.match(".*Customize the AI Service tenant operator deployment.*", message):
return "y"
if re.match(".*Tenant operator configuration YAML file.*", message):
return f"{tmpdir}/aiservice-tenant-operator-config.yaml"
if re.match(".*Watsonxai api key.*", message):
return "testWxApiKey"
if re.match(".*Watsonxai machine learning url.*", message):
Expand Down
4 changes: 4 additions & 0 deletions tekton/src/params/install-aiservice.yml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@
type: string
description: Path to YAML file that contains scheduling constraints for AI workloads for tenant
default: ""
- name: tenant_operator_cfg_file
type: string
description: Path to YAML file that contains tenant operator customization settings
default: ""

# MAS Application Configuration - IBM Maximo AI Service - Watstonx
# -----------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,9 @@ spec:
- name: tenant_scheduling_config_yaml
type: string
default: ""
- name: tenant_operator_config
type: string
default: ""

# standalone sls
- name: sls_service
Expand Down Expand Up @@ -204,5 +207,7 @@ spec:
value: $(params.tenant_entitlement_end_date)
- name: tenant_scheduling_config_yaml
value: $(params.tenant_scheduling_config_yaml)
- name: tenant_operator_config
value: $(params.tenant_operator_config)
- name: sls_service
value: $(params.sls_service)
3 changes: 3 additions & 0 deletions tekton/src/pipelines/taskdefs/aiservice/aiservice.yml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@
- name: tenant_scheduling_cfg_file
value: $(params.tenant_scheduling_cfg_file)

- name: tenant_operator_cfg_file
value: $(params.tenant_operator_cfg_file)

# Database Configuration
- name: install_db2
value: $(params.install_db2)
Expand Down
6 changes: 6 additions & 0 deletions tekton/src/tasks/aiservice/aiservice.yml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ spec:
- name: tenant_scheduling_cfg_file
type: string

- name: tenant_operator_cfg_file
type: string

# RSL
- name: rsl_ca_crt
type: string
Expand Down Expand Up @@ -266,6 +269,9 @@ spec:
value: $(params.tenant_entitlement_end_date)
- name: AISERVICE_TENANT_SCHEDULING_CONFIG_FILE
value: $(params.tenant_scheduling_cfg_file)

- name: AISERVICE_TENANT_OPERATOR_CONFIG_FILE
value: $(params.tenant_operator_cfg_file)

# RSL
- name: RSL_CA_CRT
Expand Down
Loading
Loading