From bbe69e9ea5ceecd5a7788027a963185163d79aed Mon Sep 17 00:00:00 2001 From: Bhautik Vala Date: Wed, 24 Sep 2025 17:05:09 +0530 Subject: [PATCH 1/5] [patch] Initial changes for supporting mas upgrade for AI Service --- src/mas/devops/mas.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/mas/devops/mas.py b/src/mas/devops/mas.py index 4a7ca8c6..e1151752 100644 --- a/src/mas/devops/mas.py +++ b/src/mas/devops/mas.py @@ -178,6 +178,24 @@ def verifyMasInstance(dynClient: DynamicClient, instanceId: str) -> bool: logger.error(f"Error: Unable to verify MAS instance due to failed authorization: {e}") return False +def verifyAiServiceInstance(dynClient: DynamicClient, instanceId: str) -> bool: + """ + Validate that the chosen AI Service instance exists + """ + try: + aiserviceAPI = dynClient.resources.get(api_version="aiservice.ibm.com/v1", kind="AIServiceApp") + aiserviceAPI.get(name=instanceId, namespace=f"aiservice-{instanceId}") + return True + except NotFoundError: + print("NOT FOUND") + return False + except ResourceNotFoundError: + # The AIServiceApp CRD has not even been installed in the cluster + print("RESOURCE NOT FOUND") + return False + except UnauthorizedError as e: + logger.error(f"Error: Unable to verify AI Service instance due to failed authorization: {e}") + return False def verifyAppInstance(dynClient: DynamicClient, instanceId: str, applicationId: str) -> bool: """ @@ -224,6 +242,15 @@ def getMasChannel(dynClient: DynamicClient, instanceId: str) -> str: else: return masSubscription.spec.channel +def getAiserviceChannel(dynClient: DynamicClient, instanceId: str) -> str: + """ + Get the AI Service channel from the subscription + """ + aiserviceSubscription = getSubscription(dynClient, f"aiservice-{instanceId}", "ibm-aiservice") + if aiserviceSubscription is None: + return None + else: + return aiserviceSubscription.spec.channel def updateIBMEntitlementKey(dynClient: DynamicClient, namespace: str, icrUsername: str, icrPassword: str, artifactoryUsername: str = None, artifactoryPassword: str = None, secretName: str = "ibm-entitlement") -> ResourceInstance: if secretName is None: From 2eabe08d575c671caebcd1d90f6f49444bd3aa15 Mon Sep 17 00:00:00 2001 From: Bhautik Vala Date: Wed, 24 Sep 2025 17:22:13 +0530 Subject: [PATCH 2/5] [patch] Fix precommit issue --- src/mas/devops/mas.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mas/devops/mas.py b/src/mas/devops/mas.py index e1151752..89402d67 100644 --- a/src/mas/devops/mas.py +++ b/src/mas/devops/mas.py @@ -178,6 +178,7 @@ def verifyMasInstance(dynClient: DynamicClient, instanceId: str) -> bool: logger.error(f"Error: Unable to verify MAS instance due to failed authorization: {e}") return False + def verifyAiServiceInstance(dynClient: DynamicClient, instanceId: str) -> bool: """ Validate that the chosen AI Service instance exists @@ -197,6 +198,7 @@ def verifyAiServiceInstance(dynClient: DynamicClient, instanceId: str) -> bool: logger.error(f"Error: Unable to verify AI Service instance due to failed authorization: {e}") return False + def verifyAppInstance(dynClient: DynamicClient, instanceId: str, applicationId: str) -> bool: """ Validate that the chosen app instance exists @@ -242,6 +244,7 @@ def getMasChannel(dynClient: DynamicClient, instanceId: str) -> str: else: return masSubscription.spec.channel + def getAiserviceChannel(dynClient: DynamicClient, instanceId: str) -> str: """ Get the AI Service channel from the subscription @@ -252,6 +255,7 @@ def getAiserviceChannel(dynClient: DynamicClient, instanceId: str) -> str: else: return aiserviceSubscription.spec.channel + def updateIBMEntitlementKey(dynClient: DynamicClient, namespace: str, icrUsername: str, icrPassword: str, artifactoryUsername: str = None, artifactoryPassword: str = None, secretName: str = "ibm-entitlement") -> ResourceInstance: if secretName is None: secretName = "ibm-entitlement" From 39a228264680e0bd874f714eaa99ee561245c1d1 Mon Sep 17 00:00:00 2001 From: Bhautik Vala Date: Fri, 26 Sep 2025 17:06:54 +0530 Subject: [PATCH 3/5] [patch] Add launchAiServiceUpgradePipeline function --- src/mas/devops/tekton.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/mas/devops/tekton.py b/src/mas/devops/tekton.py index 3b4eb1eb..0751238f 100644 --- a/src/mas/devops/tekton.py +++ b/src/mas/devops/tekton.py @@ -462,3 +462,35 @@ def launchAiServiceInstallPipeline(dynClient: DynamicClient, params: dict) -> st pipelineURL = f"{getConsoleURL(dynClient)}/k8s/ns/aiservice-{instanceId}-pipelines/tekton.dev~v1beta1~PipelineRun/{instanceId}-install-{timestamp}" return pipelineURL + + +def launchAiServiceUpgradePipeline(dynClient: DynamicClient, + aiserviceInstanceId: str, + skipPreCheck: bool = False, + masChannel: str = "", + params: dict = {}) -> str: + """ + Create a PipelineRun to upgrade the chosen AI Service instance + """ + pipelineRunsAPI = dynClient.resources.get(api_version="tekton.dev/v1beta1", kind="PipelineRun") + namespace = f"aiservice-{aiserviceInstanceId}-pipelines" + timestamp = datetime.now().strftime("%y%m%d-%H%M") + # Create the PipelineRun + templateDir = path.join(path.abspath(path.dirname(__file__)), "templates") + env = Environment( + loader=FileSystemLoader(searchpath=templateDir) + ) + template = env.get_template("pipelinerun-upgrade.yml.j2") + renderedTemplate = template.render( + timestamp=timestamp, + mas_instance_id=aiserviceInstanceId, + skip_pre_check=skipPreCheck, + mas_channel=masChannel, + **params + ) + logger.debug(renderedTemplate) + pipelineRun = yaml.safe_load(renderedTemplate) + pipelineRunsAPI.apply(body=pipelineRun, namespace=namespace) + + pipelineURL = f"{getConsoleURL(dynClient)}/k8s/ns/aiservice-{aiserviceInstanceId}-pipelines/tekton.dev~v1beta1~PipelineRun/{aiserviceInstanceId}-upgrade-{timestamp}" + return pipelineURL \ No newline at end of file From 3365f8d9000d7a0d5dc2848756a4d595c363da22 Mon Sep 17 00:00:00 2001 From: Bhautik Vala Date: Mon, 29 Sep 2025 14:39:43 +0530 Subject: [PATCH 4/5] [patch] Fix pre-commit issue --- src/mas/devops/tekton.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/mas/devops/tekton.py b/src/mas/devops/tekton.py index 0751238f..1fccf9bb 100644 --- a/src/mas/devops/tekton.py +++ b/src/mas/devops/tekton.py @@ -465,10 +465,10 @@ def launchAiServiceInstallPipeline(dynClient: DynamicClient, params: dict) -> st def launchAiServiceUpgradePipeline(dynClient: DynamicClient, - aiserviceInstanceId: str, - skipPreCheck: bool = False, - masChannel: str = "", - params: dict = {}) -> str: + aiserviceInstanceId: str, + skipPreCheck: bool = False, + masChannel: str = "", + params: dict = {}) -> str: """ Create a PipelineRun to upgrade the chosen AI Service instance """ @@ -493,4 +493,4 @@ def launchAiServiceUpgradePipeline(dynClient: DynamicClient, pipelineRunsAPI.apply(body=pipelineRun, namespace=namespace) pipelineURL = f"{getConsoleURL(dynClient)}/k8s/ns/aiservice-{aiserviceInstanceId}-pipelines/tekton.dev~v1beta1~PipelineRun/{aiserviceInstanceId}-upgrade-{timestamp}" - return pipelineURL \ No newline at end of file + return pipelineURL From a8629cb738e46800a143cbf478649e722225a10b Mon Sep 17 00:00:00 2001 From: Bhautik Vala Date: Fri, 10 Oct 2025 15:29:16 +0530 Subject: [PATCH 5/5] [patch] Add aiservice-upgrade pipeline: --- src/mas/devops/tekton.py | 8 +-- .../pipelinerun-aiservice-upgrade.yml.j2 | 56 +++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 src/mas/devops/templates/pipelinerun-aiservice-upgrade.yml.j2 diff --git a/src/mas/devops/tekton.py b/src/mas/devops/tekton.py index 1fccf9bb..f489d5b5 100644 --- a/src/mas/devops/tekton.py +++ b/src/mas/devops/tekton.py @@ -467,7 +467,7 @@ def launchAiServiceInstallPipeline(dynClient: DynamicClient, params: dict) -> st def launchAiServiceUpgradePipeline(dynClient: DynamicClient, aiserviceInstanceId: str, skipPreCheck: bool = False, - masChannel: str = "", + aiserviceChannel: str = "", params: dict = {}) -> str: """ Create a PipelineRun to upgrade the chosen AI Service instance @@ -480,12 +480,12 @@ def launchAiServiceUpgradePipeline(dynClient: DynamicClient, env = Environment( loader=FileSystemLoader(searchpath=templateDir) ) - template = env.get_template("pipelinerun-upgrade.yml.j2") + template = env.get_template("pipelinerun-aiservice-upgrade.yml.j2") renderedTemplate = template.render( timestamp=timestamp, - mas_instance_id=aiserviceInstanceId, + aiservice_instance_id=aiserviceInstanceId, skip_pre_check=skipPreCheck, - mas_channel=masChannel, + aiservice_channel=aiserviceChannel, **params ) logger.debug(renderedTemplate) diff --git a/src/mas/devops/templates/pipelinerun-aiservice-upgrade.yml.j2 b/src/mas/devops/templates/pipelinerun-aiservice-upgrade.yml.j2 new file mode 100644 index 00000000..47bfe1d6 --- /dev/null +++ b/src/mas/devops/templates/pipelinerun-aiservice-upgrade.yml.j2 @@ -0,0 +1,56 @@ +--- +apiVersion: tekton.dev/v1beta1 +kind: PipelineRun +metadata: + name: "{{ aiservice_instance_id }}-upgrade-{{ timestamp }}" + labels: + tekton.dev/pipeline: aiservice-upgrade +spec: + pipelineRef: + name: aiservice-upgrade + + serviceAccountName: "{{ service_account_name | default('pipeline', True) }}" + timeouts: + pipeline: "0" + + params: + # Target AI Service Instance + # ------------------------------------------------------------------------- + - name: aiservice_instance_id + value: "{{ aiservice_instance_id }}" + - name: aiservice_channel + value: "{{ aiservice_channel }}" + + # IBM Entitlement Key + # ------------------------------------------------------------------------- + - name: ibm_entitlement_key + value: "{{ ibm_entitlement_key }}" + +{%- if skip_pre_check is defined and skip_pre_check != "" %} + # Skip pre-check + # ------------------------------------------------------------------------- + - name: skip_pre_check + value: "{{ skip_pre_check }}" +{%- endif %} +{%- if artifactory_username is defined and artifactory_username != "" %} + + # Enable development catalogs + # ------------------------------------------------------------------------- + - name: artifactory_username + value: "{{ artifactory_username }}" + - name: artifactory_token + value: "{{ artifactory_token }}" +{%- endif %} + + workspaces: + # The generated configuration files + # ------------------------------------------------------------------------- + - name: shared-configs + persistentVolumeClaim: + claimName: config-pvc + + # PodTemplates configurations + # ------------------------------------------------------------------------- + - name: shared-pod-templates + secret: + secretName: pipeline-pod-templates