From ac3649bc8c2cfa9db90fd81199801b98747b6b6c Mon Sep 17 00:00:00 2001 From: Anil Prajapati Date: Wed, 22 Apr 2026 10:31:23 +0530 Subject: [PATCH 1/3] [patch] add prepareAIServiceInstallSecrets --- src/mas/devops/tekton.py | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/mas/devops/tekton.py b/src/mas/devops/tekton.py index e28fff1b..b742f916 100644 --- a/src/mas/devops/tekton.py +++ b/src/mas/devops/tekton.py @@ -678,6 +678,74 @@ def prepareUpdateSlackSecrets(dynClient: DynamicClient, slack_token: str = None, logger.info(f"Created mas-devops-slack secret in namespace {namespace}") +def prepareAIServiceInstallSecrets(dynClient: DynamicClient, instanceId: str, slack_token: str = None, slack_channel: str = None) -> None: + """ + Create or update mas-devops-slack secret in aiservice-{instanceId}-pipelines namespace for AI Service install pipeline. + + Creates the slack secret in aiservice-{instanceId}-pipelines namespace if it exists and slack credentials are provided. + This function is specifically for AI Service installations which use a different namespace pattern than MAS installations. + + Parameters: + dynClient (DynamicClient): OpenShift Dynamic Client + instanceId (str): AI Service instance ID + slack_token (str, optional): Slack bot token for notifications. Defaults to None. + slack_channel (str, optional): Slack channel ID for notifications. Defaults to None. + + Returns: + None + + Raises: + NotFoundError: If namespace doesn't exist (will be caught and logged) + """ + namespace = f"aiservice-{instanceId}-pipelines" + + # Check if namespace exists + try: + namespaceAPI = dynClient.resources.get(api_version="v1", kind="Namespace") + namespaceAPI.get(name=namespace) + except NotFoundError: + logger.warning(f"Namespace {namespace} does not exist, skipping slack secret creation") + return + + # Only create secret if both slack_token and slack_channel are provided + if not slack_token or not slack_channel: + logger.debug("Slack token or channel not provided, skipping slack secret creation") + return + + secretsAPI = dynClient.resources.get(api_version="v1", kind="Secret") + + # Delete existing secret if it exists + try: + secretsAPI.delete(name="mas-devops-slack", namespace=namespace) + except NotFoundError: + pass + + # Create the secret with MAS_INSTANCE_ID, SLACK_TOKEN and SLACK_CHANNEL + # Note: We use MAS_INSTANCE_ID (not AISERVICE_INSTANCE_ID) to maintain consistency with MAS install secrets + secret_data = { + "MAS_INSTANCE_ID": base64.b64encode(instanceId.encode()).decode() + } + + if slack_token: + secret_data["SLACK_TOKEN"] = base64.b64encode(slack_token.encode()).decode() + + if slack_channel: + secret_data["SLACK_CHANNEL"] = base64.b64encode(slack_channel.encode()).decode() + + mas_devops_secret = { + "apiVersion": "v1", + "kind": "Secret", + "type": "Opaque", + "metadata": { + "name": "mas-devops-slack" + }, + "data": secret_data + } + + secretsAPI.create(body=mas_devops_secret, namespace=namespace) + logger.info(f"Created mas-devops-slack secret with MAS_INSTANCE_ID={instanceId} in namespace {namespace}") + + def testCLI() -> None: pass # echo -n "Testing availability of $CLI_IMAGE in cluster ..." From bba90a8611520002f7652d9cf16fef3731e40f76 Mon Sep 17 00:00:00 2001 From: Anil Prajapati Date: Fri, 1 May 2026 21:58:11 +0530 Subject: [PATCH 2/3] [patch] add namespace argument --- bin/mas-devops-notify-slack | 82 +++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/bin/mas-devops-notify-slack b/bin/mas-devops-notify-slack index 715c5aab..ba6a148a 100755 --- a/bin/mas-devops-notify-slack +++ b/bin/mas-devops-notify-slack @@ -97,19 +97,21 @@ def notifyProvisionRoks(channels: list[str], rc: int, additionalMsg: str | None return response.data.get("ok", False) -def notifyPipelineStart(channels: list[str], instanceId: str | None = None, pipelineName: str | None = None) -> dict | None: +def notifyPipelineStart(channels: list[str], instanceId: str | None = None, pipelineName: str | None = None, namespace: str | None = None) -> dict | None: """Send Slack notification about pipeline start and create thread for all channels.""" # Exit early if no channels provided if not channels or len(channels) == 0: print("No Slack channels provided - skipping pipeline start notification") return None - # For update pipeline, use mas-pipelines namespace (no instance ID) - # For install/upgrade pipelines, use mas-{instanceId}-pipelines namespace - if instanceId is None or instanceId == "": - namespace = "mas-pipelines" - else: - namespace = f"mas-{instanceId}-pipelines" + # Use provided namespace, or fall back to legacy logic for backward compatibility + if namespace is None or namespace == "": + # For update pipeline, use mas-pipelines namespace (no instance ID) + # For install/upgrade pipelines, use mas-{instanceId}-pipelines namespace + if instanceId is None or instanceId == "": + namespace = "mas-pipelines" + else: + namespace = f"mas-{instanceId}-pipelines" # Check if thread already exists threadInfo = SlackUtil.getThreadConfigMap(namespace, instanceId, pipelineName) @@ -157,25 +159,27 @@ def notifyPipelineStart(channels: list[str], instanceId: str | None = None, pipe return SlackUtil.getThreadConfigMap(namespace, instanceId, pipelineName) -def notifyAnsibleStart(channels: list[str], taskName: str, instanceId: str | None = None, pipelineName: str | None = None) -> bool: +def notifyAnsibleStart(channels: list[str], taskName: str, instanceId: str | None = None, pipelineName: str | None = None, namespace: str | None = None) -> bool: """Send Slack notification about Ansible task start to all channels.""" # Exit early if no channels provided if not channels or len(channels) == 0: print("No Slack channels provided - skipping Ansible task start notification") return False - # For update pipeline, use mas-pipelines namespace (no instance ID) - # For install/upgrade pipelines, use mas-{instanceId}-pipelines namespace - if instanceId is None or instanceId == "": - namespace = "mas-pipelines" - else: - namespace = f"mas-{instanceId}-pipelines" + # Use provided namespace, or fall back to legacy logic for backward compatibility + if namespace is None or namespace == "": + # For update pipeline, use mas-pipelines namespace (no instance ID) + # For install/upgrade pipelines, use mas-{instanceId}-pipelines namespace + if instanceId is None or instanceId == "": + namespace = "mas-pipelines" + else: + namespace = f"mas-{instanceId}-pipelines" # Get thread information, create if doesn't exist threadInfo = SlackUtil.getThreadConfigMap(namespace, instanceId, pipelineName) if threadInfo is None: print("No thread found - creating pipeline start notification") - threadInfo = notifyPipelineStart(channels, instanceId, pipelineName) + threadInfo = notifyPipelineStart(channels, instanceId, pipelineName, namespace) # Get channel count channelCount = int(threadInfo.get("channel_count", "0")) @@ -216,25 +220,27 @@ def notifyAnsibleStart(channels: list[str], taskName: str, instanceId: str | Non return allSuccess -def notifyAnsibleComplete(channels: list[str], rc: int, taskName: str, instanceId: str | None = None, pipelineName: str | None = None) -> bool: +def notifyAnsibleComplete(channels: list[str], rc: int, taskName: str, instanceId: str | None = None, pipelineName: str | None = None, namespace: str | None = None) -> bool: """Send Slack notification about Ansible task completion status to all channels.""" # Exit early if no channels provided if not channels or len(channels) == 0: print("No Slack channels provided - skipping Ansible task completion notification") return False - # For update pipeline, use mas-pipelines namespace (no instance ID) - # For install/upgrade pipelines, use mas-{instanceId}-pipelines namespace - if instanceId is None or instanceId == "": - namespace = "mas-pipelines" - else: - namespace = f"mas-{instanceId}-pipelines" + # Use provided namespace, or fall back to legacy logic for backward compatibility + if namespace is None or namespace == "": + # For update pipeline, use mas-pipelines namespace (no instance ID) + # For install/upgrade pipelines, use mas-{instanceId}-pipelines namespace + if instanceId is None or instanceId == "": + namespace = "mas-pipelines" + else: + namespace = f"mas-{instanceId}-pipelines" # Get thread information, create if doesn't exist threadInfo = SlackUtil.getThreadConfigMap(namespace, instanceId, pipelineName) if threadInfo is None: print("No thread found - creating pipeline start notification") - threadInfo = notifyPipelineStart(channels, instanceId, pipelineName) + threadInfo = notifyPipelineStart(channels, instanceId, pipelineName, namespace) # Get channel count channelCount = int(threadInfo.get("channel_count", "0")) @@ -306,24 +312,26 @@ def notifyAnsibleComplete(channels: list[str], rc: int, taskName: str, instanceI # Special case, mas-update pipeline if namespace == "mas-pipelines" and taskName == "post-deps-update-verify-ingress": print(f"mas-update pipeline completed with status: {rc}, sending pipeline complete message") - allSuccess: bool = notifyPipelineComplete(channels, rc, instanceId, pipelineName) + allSuccess: bool = notifyPipelineComplete(channels, rc, instanceId, pipelineName, namespace) return allSuccess -def notifyPipelineComplete(channels: list[str], rc: int, instanceId: str | None = None, pipelineName: str | None = None) -> bool: +def notifyPipelineComplete(channels: list[str], rc: int, instanceId: str | None = None, pipelineName: str | None = None, namespace: str | None = None) -> bool: """Send Slack notification about pipeline completion to all channels and cleanup ConfigMap.""" # Exit early if no channels provided if not channels or len(channels) == 0: print("No Slack channels provided - skipping pipeline completion notification") return False - # For update pipeline, use mas-pipelines namespace (no instance ID) - # For install/upgrade pipelines, use mas-{instanceId}-pipelines namespace - if instanceId is None or instanceId == "": - namespace = "mas-pipelines" - else: - namespace = f"mas-{instanceId}-pipelines" + # Use provided namespace, or fall back to legacy logic for backward compatibility + if namespace is None or namespace == "": + # For update pipeline, use mas-pipelines namespace (no instance ID) + # For install/upgrade pipelines, use mas-{instanceId}-pipelines namespace + if instanceId is None or instanceId == "": + namespace = "mas-pipelines" + else: + namespace = f"mas-{instanceId}-pipelines" # Get thread information threadInfo = SlackUtil.getThreadConfigMap(namespace, instanceId, pipelineName) @@ -411,18 +419,22 @@ if __name__ == "__main__": parser.add_argument("--task-name", required=False, default="") parser.add_argument("--instance-id", required=False, default=None) parser.add_argument("--pipeline-name", required=False, default=None) + parser.add_argument("--namespace", required=False, default=None, help="Pipeline namespace (e.g., mas-{instanceId}-pipelines or aiservice-{instanceId}-pipelines)") args, unknown = parser.parse_known_args() + # Use namespace from command line arg, or fall back to PIPELINE_NAMESPACE env var + namespace = args.namespace if args.namespace else os.getenv("PIPELINE_NAMESPACE", None) + if args.action == "ocp-provision-fyre": notifyProvisionFyre(channelList, args.rc, args.msg) elif args.action == "ocp-provision-roks": notifyProvisionRoks(channelList, args.rc, args.msg) elif args.action == "pipeline-start": - notifyPipelineStart(channelList, args.instance_id, args.pipeline_name) + notifyPipelineStart(channelList, args.instance_id, args.pipeline_name, namespace) elif args.action == "ansible-start": - notifyAnsibleStart(channelList, args.task_name, args.instance_id, args.pipeline_name) + notifyAnsibleStart(channelList, args.task_name, args.instance_id, args.pipeline_name, namespace) elif args.action == "ansible-complete": - notifyAnsibleComplete(channelList, args.rc, args.task_name, args.instance_id, args.pipeline_name) + notifyAnsibleComplete(channelList, args.rc, args.task_name, args.instance_id, args.pipeline_name, namespace) elif args.action == "pipeline-complete": - notifyPipelineComplete(channelList, args.rc, args.instance_id, args.pipeline_name) + notifyPipelineComplete(channelList, args.rc, args.instance_id, args.pipeline_name, namespace) From 7466249140a7fadd415158def7fef898ba7a8692 Mon Sep 17 00:00:00 2001 From: Anil Prajapati Date: Wed, 6 May 2026 12:50:54 +0530 Subject: [PATCH 3/3] [patch] use existing prepare secret func --- src/mas/devops/tekton.py | 80 ++++------------------------------------ 1 file changed, 8 insertions(+), 72 deletions(-) diff --git a/src/mas/devops/tekton.py b/src/mas/devops/tekton.py index 5443cc97..1df7bc8d 100644 --- a/src/mas/devops/tekton.py +++ b/src/mas/devops/tekton.py @@ -9,6 +9,7 @@ # ***************************************************************************** import logging +import re import yaml import base64 @@ -572,7 +573,7 @@ def prepareInstallSecrets(dynClient: DynamicClient, namespace: str, slsLicenseFi Parameters: dynClient (DynamicClient): OpenShift Dynamic Client - namespace (str): The namespace to create secrets in (format: mas-{instance_id}-pipelines) + namespace (str): The namespace to create secrets in (format: mas-{instance_id}-pipelines or aiservice-{instance_id}-pipelines) slsLicenseFile (str, optional): SLS license file content. Defaults to None (empty secret). additionalConfigs (dict, optional): Additional configuration data. Defaults to None (empty secret). certs (str, optional): Certificate data. Defaults to None (empty secret). @@ -588,10 +589,13 @@ def prepareInstallSecrets(dynClient: DynamicClient, namespace: str, slsLicenseFi """ secretsAPI = dynClient.resources.get(api_version="v1", kind="Secret") - # Extract instance ID from namespace (format: mas-{instance_id}-pipelines) + # Extract instance ID from namespace using regex + # Supports both formats: mas-{instance_id}-pipelines and aiservice-{instance_id}-pipelines instance_id = None - if namespace.startswith("mas-") and namespace.endswith("-pipelines"): - instance_id = namespace[4:-10] # Remove "mas-" prefix and "-pipelines" suffix + namespace_pattern = r'^(?:mas|aiservice)-(.+)-pipelines$' + match = re.match(namespace_pattern, namespace) + if match: + instance_id = match.group(1) # 0. Secret/mas-devops-slack # ------------------------------------------------------------------------- @@ -765,74 +769,6 @@ def prepareUpdateSlackSecrets(dynClient: DynamicClient, slack_token: str = None, logger.info(f"Created mas-devops-slack secret in namespace {namespace}") -def prepareAIServiceInstallSecrets(dynClient: DynamicClient, instanceId: str, slack_token: str = None, slack_channel: str = None) -> None: - """ - Create or update mas-devops-slack secret in aiservice-{instanceId}-pipelines namespace for AI Service install pipeline. - - Creates the slack secret in aiservice-{instanceId}-pipelines namespace if it exists and slack credentials are provided. - This function is specifically for AI Service installations which use a different namespace pattern than MAS installations. - - Parameters: - dynClient (DynamicClient): OpenShift Dynamic Client - instanceId (str): AI Service instance ID - slack_token (str, optional): Slack bot token for notifications. Defaults to None. - slack_channel (str, optional): Slack channel ID for notifications. Defaults to None. - - Returns: - None - - Raises: - NotFoundError: If namespace doesn't exist (will be caught and logged) - """ - namespace = f"aiservice-{instanceId}-pipelines" - - # Check if namespace exists - try: - namespaceAPI = dynClient.resources.get(api_version="v1", kind="Namespace") - namespaceAPI.get(name=namespace) - except NotFoundError: - logger.warning(f"Namespace {namespace} does not exist, skipping slack secret creation") - return - - # Only create secret if both slack_token and slack_channel are provided - if not slack_token or not slack_channel: - logger.debug("Slack token or channel not provided, skipping slack secret creation") - return - - secretsAPI = dynClient.resources.get(api_version="v1", kind="Secret") - - # Delete existing secret if it exists - try: - secretsAPI.delete(name="mas-devops-slack", namespace=namespace) - except NotFoundError: - pass - - # Create the secret with MAS_INSTANCE_ID, SLACK_TOKEN and SLACK_CHANNEL - # Note: We use MAS_INSTANCE_ID (not AISERVICE_INSTANCE_ID) to maintain consistency with MAS install secrets - secret_data = { - "MAS_INSTANCE_ID": base64.b64encode(instanceId.encode()).decode() - } - - if slack_token: - secret_data["SLACK_TOKEN"] = base64.b64encode(slack_token.encode()).decode() - - if slack_channel: - secret_data["SLACK_CHANNEL"] = base64.b64encode(slack_channel.encode()).decode() - - mas_devops_secret = { - "apiVersion": "v1", - "kind": "Secret", - "type": "Opaque", - "metadata": { - "name": "mas-devops-slack" - }, - "data": secret_data - } - - secretsAPI.create(body=mas_devops_secret, namespace=namespace) - logger.info(f"Created mas-devops-slack secret with MAS_INSTANCE_ID={instanceId} in namespace {namespace}") - - def testCLI() -> None: pass # echo -n "Testing availability of $CLI_IMAGE in cluster ..."