Skip to content
97 changes: 97 additions & 0 deletions src/mas/devops/tekton.py
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,83 @@ def prepareAiServicePipelinesNamespace(
logger.info(f"Storage class {storageClass} uses volumeBindingMode={volumeBindingMode}, skipping PVC bind wait")


def prepareMcpiPipelinesNamespace(
dynClient: DynamicClient,
instanceId: str = None,
storageClass: str = None,
accessMode: str = None,
waitForBind: bool = True,
configureRBAC: bool = True,
):
"""
Prepare a namespace for MCPI pipelines by creating RBAC and PVC resources.

Creates MCPI-specific pipeline namespace with necessary role bindings
and persistent volume claims.

Args:
dynClient (DynamicClient): OpenShift Dynamic Client
instanceId (str, optional): MCPI instance ID. Defaults to None.
storageClass (str, optional): Storage class for the PVC. Defaults to None.
accessMode (str, optional): Access mode for the PVC. Defaults to None.
waitForBind (bool, optional): Whether to wait for PVC to bind. Defaults to True.
configureRBAC (bool, optional): Whether to configure RBAC. Defaults to True.

Raises:
NotFoundError: If resources cannot be created
"""
templateDir = path.join(path.abspath(path.dirname(__file__)), "templates")
env = Environment(loader=FileSystemLoader(searchpath=templateDir))
namespace = f"mcpi-{instanceId}-pipelines"
template = env.get_template("mcpi-pipelines-rbac.yml.j2")

if configureRBAC:
renderedTemplate = template.render(mcpi_instance_id=instanceId)
logger.debug(renderedTemplate)
crb = yaml.safe_load(renderedTemplate)
applyResource(
dynClient=dynClient,
apiVersion="rbac.authorization.k8s.io/v1",
kind="ClusterRoleBinding",
body=crb,
namespace=namespace,
)

template = env.get_template("mcpi-pipelines-pvc.yml.j2")
renderedTemplate = template.render(
mcpi_instance_id=instanceId,
pipeline_storage_class=storageClass,
pipeline_storage_accessmode=accessMode,
)
logger.debug(renderedTemplate)
pvc = yaml.safe_load(renderedTemplate)
pvcAPI = dynClient.resources.get(api_version="v1", kind="PersistentVolumeClaim")
applyResource(
dynClient=dynClient,
apiVersion="v1",
kind="PersistentVolumeClaim",
body=pvc,
namespace=namespace,
)

volumeBindingMode = getStorageClassVolumeBindingMode(dynClient, storageClass)
waitForBind = volumeBindingMode == "Immediate"

if waitForBind:
logger.info(f"Storage class {storageClass} uses volumeBindingMode={volumeBindingMode}, waiting for PVC to bind")
pvcIsBound = False
while not pvcIsBound:
configPVC = pvcAPI.get(name="config-pvc", namespace=namespace)
if configPVC.status.phase == "Bound":
pvcIsBound = True
else:
logger.debug("Waiting 15s before checking status of PVC again")
logger.debug(configPVC)
sleep(15)
else:
logger.info(f"Storage class {storageClass} uses volumeBindingMode={volumeBindingMode}, skipping PVC bind wait")


def prepareRestoreSecrets(dynClient: DynamicClient, namespace: str, restoreConfigs: dict = None):
"""
Create or update secret required for MAS Restore pipeline.
Expand Down Expand Up @@ -1342,6 +1419,26 @@ def launchInstallPipeline(dynClient: DynamicClient, params: dict) -> str:
return pipelineURL


def launchMcpiInstallPipeline(dynClient: DynamicClient, params: dict) -> str:
"""
Create a PipelineRun to install an MCPI instance.

Args:
dynClient (DynamicClient): OpenShift Dynamic Client
params (dict): Installation parameters including mas_instance_id and mcpi_channel

Returns:
str: URL to the PipelineRun in the OpenShift console

Raises:
NotFoundError: If resources cannot be created
"""
instanceId = params["mas_instance_id"]
namespace = f"mcpi-{instanceId}-pipelines"
timestamp = launchPipelineRun(dynClient, namespace, "pipelinerun-mcpi-install", params)
return f"{getConsoleURL(dynClient)}/k8s/ns/mcpi-{instanceId}-pipelines/tekton.dev~v1beta1~PipelineRun/{instanceId}-install-{timestamp}"


def launchUpdatePipeline(dynClient: DynamicClient, params: dict) -> str:
"""
Create a PipelineRun to update the Maximo Operator Catalog.
Expand Down
15 changes: 15 additions & 0 deletions src/mas/devops/templates/mcpi-pipelines-pvc.yml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
# 1. Set up a PVC for shared storage
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: config-pvc
namespace: mcpi-{{ mcpi_instance_id }}-pipelines
spec:
accessModes:
- {{ pipeline_storage_accessmode }}
volumeMode: Filesystem
storageClassName: {{ pipeline_storage_class }}
resources:
requests:
storage: 500Mi
14 changes: 14 additions & 0 deletions src/mas/devops/templates/mcpi-pipelines-rbac.yml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
# 1. Configure RBAC for the pipeline tasks
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: mcpi-pipeline-{{ mcpi_instance_id }}
subjects:
- kind: ServiceAccount
name: pipeline
namespace: mcpi-{{ mcpi_instance_id }}-pipelines
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
150 changes: 150 additions & 0 deletions src/mas/devops/templates/pipelinerun-mcpi-install.yml.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
---
apiVersion: tekton.dev/v1
kind: PipelineRun
metadata:
name: "{{mas_instance_id}}-install-{{ timestamp }}"
labels:
tekton.dev/pipeline: mcpi-install
spec:
pipelineRef:
name: mcpi-install

taskRunTemplate:
serviceAccountName: "{{ service_account_name | default('pipeline', True) }}"
timeouts:
pipeline: "0"

params:
# IBM Entitlement Key
# -------------------------------------------------------------------------
- name: ibm_entitlement_key
value: "{{ ibm_entitlement_key }}"
{%- if skip_pre_check is defined and skip_pre_check != "" %}

# Pipeline config
# -------------------------------------------------------------------------
- name: skip_pre_check
value: "{{ skip_pre_check }}"
{%- endif %}
{%- if image_pull_policy is defined and image_pull_policy != "" %}

# Image Pull Policy
# -------------------------------------------------------------------------
- name: image_pull_policy
value: "{{ image_pull_policy }}"
{%- 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 %}

# Storage Classes
# -------------------------------------------------------------------------
- name: storage_class_rwx
value: "{{ storage_class_rwx }}"
- name: storage_class_rwo
value: "{{ storage_class_rwo }}"

# Dependencies - SLS
# -------------------------------------------------------------------------
{%- if sls_channel is defined and sls_channel != "" %}
- name: sls_channel
value: "{{ sls_channel }}"
{%- endif %}
{%- if sls_entitlement_file is defined and sls_entitlement_file != "" %}
- name: sls_entitlement_file
value: "{{ sls_entitlement_file }}"
{%- endif %}
{%- if sls_namespace is defined and sls_namespace != "" %}
- name: sls_namespace
value: "{{ sls_namespace }}"
{%- endif %}
{%- if sls_action is defined and sls_action != "" %}
- name: sls_action
value: "{{ sls_action }}"
{%- endif %}

# Dependencies - DRO (Required)
# -------------------------------------------------------------------------
- name: dro_action
value: "{{ dro_action }}"
- name: dro_contact_email
value: "{{ dro_contact_email }}"
- name: dro_contact_firstname
value: "{{ dro_contact_firstname }}"
- name: dro_contact_lastname
value: "{{ dro_contact_lastname }}"
{%- if dro_namespace is defined and dro_namespace != "" %}
- name: dro_namespace
value: "{{ dro_namespace }}"
{%- endif %}

# Dependencies - Certificate Manager
# -------------------------------------------------------------------------
- name: cert_manager_provider
value: "{{ cert_manager_provider }}"
- name: cert_manager_action
value: "{{ cert_manager_action }}"

# MAS Catalog
# -------------------------------------------------------------------------
- name: mas_catalog_version
value: "{{ mas_catalog_version }}"
{%- if mas_catalog_digest is defined and mas_catalog_digest != "" %}
- name: mas_catalog_digest
value: "{{ mas_catalog_digest }}"
{%- endif %}

# MAS Core
# -------------------------------------------------------------------------
- name: mas_instance_id
value: "{{ mas_instance_id }}"

# MCPI Add-On
# -------------------------------------------------------------------------
- name: mcpi_channel
value: "{{ mcpi_channel }}"
{%- if routing_mode is defined and routing_mode != "" %}
- name: routing_mode
value: "{{ routing_mode }}"
{%- endif %}
{%- if manual_route_mgmt is defined and manual_route_mgmt != "" %}
- name: manual_route_mgmt
value: "{{ manual_route_mgmt }}"
{%- endif %}

workspaces:
# The generated configuration files
# -------------------------------------------------------------------------
- name: shared-configs
persistentVolumeClaim:
claimName: config-pvc

# User-provided configurations
# -------------------------------------------------------------------------
- name: shared-additional-configs
secret:
secretName: pipeline-additional-configs

# SLS entitlement
# -------------------------------------------------------------------------
- name: shared-entitlement
secret:
secretName: pipeline-sls-entitlement

# PodTemplates configurations
# -------------------------------------------------------------------------
- name: shared-pod-templates
secret:
secretName: pipeline-pod-templates

# Certificates configurations
# -------------------------------------------------------------------------
- name: shared-certificates
secret:
secretName: pipeline-certificates
Loading
Loading