diff --git a/src/mas/devops/tekton.py b/src/mas/devops/tekton.py index cb91ed80..bd14871b 100644 --- a/src/mas/devops/tekton.py +++ b/src/mas/devops/tekton.py @@ -843,17 +843,30 @@ def prepareAiServicePipelinesNamespace( logger.info(f"Storage class {storageClass} uses volumeBindingMode={volumeBindingMode}, skipping PVC bind wait") -def prepareRestoreSecrets(dynClient: DynamicClient, namespace: str, restoreConfigs: dict = None): +def prepareRestoreSecrets( + dynClient: DynamicClient, + namespace: str, + restoreConfigs: dict = None, + ibm_entitlement_key: str = None, + artifactory_token: str = None, + artifactory_username: str = None, + registry_secret_name: str = "mas-restore-secrets", +): """ Create or update secret required for MAS Restore pipeline. - Creates secret in the specified namespace: + Creates secrets in the specified namespace: - pipeline-restore-configs + - {registry_secret_name} (only when credentials are provided) Parameters: dynClient (DynamicClient): OpenShift Dynamic Client namespace (str): The namespace to create secrets in restoreConfigs (dict, optional): configuration data for restore. Defaults to None (empty secret). + ibm_entitlement_key (str, optional): IBM entitlement key for registry access. Defaults to None. + artifactory_token (str, optional): Artifactory token for dev catalog access. Defaults to None. + artifactory_username (str, optional): Artifactory username for dev catalog access. Defaults to None. + registry_secret_name (str, optional): Name of the per-pipeline registry credentials secret. Defaults to "mas-restore-secrets". Returns: None @@ -880,6 +893,37 @@ def prepareRestoreSecrets(dynClient: DynamicClient, namespace: str, restoreConfi } secretsAPI.create(body=restoreConfigs, namespace=namespace) + # 2. Secret/{registry_secret_name} + # ------------------------------------------------------------------------- + credentials_data = {} + + if ibm_entitlement_key: + credentials_data["IBM_ENTITLEMENT_KEY"] = base64.b64encode(ibm_entitlement_key.encode()).decode() + + if artifactory_token: + credentials_data["ARTIFACTORY_TOKEN"] = base64.b64encode(artifactory_token.encode()).decode() + + if artifactory_username: + credentials_data["ARTIFACTORY_USERNAME"] = base64.b64encode(artifactory_username.encode()).decode() + + if credentials_data: + try: + secretsAPI.delete(name=registry_secret_name, namespace=namespace) + except NotFoundError: + pass + + secretsAPI.create( + body={ + "apiVersion": "v1", + "kind": "Secret", + "type": "Opaque", + "metadata": {"name": registry_secret_name}, + "data": credentials_data, + }, + namespace=namespace, + ) + logger.info(f"Created {registry_secret_name} secret in namespace {namespace}") + def prepareInstallSecrets( dynClient: DynamicClient, @@ -893,13 +937,18 @@ def prepareInstallSecrets( aiserviceConfig: str = None, db2LicenseFile: dict | None = None, facilitiesProperties: dict | None = None, + ibm_entitlement_key: str = None, + artifactory_token: str = None, + artifactory_username: str = None, + registry_secret_name: str = None, ) -> None: """ Create or update secrets required for MAS installation pipelines. - Creates secrets in the specified namespace: mas-devops-slack, pipeline-additional-configs, - pipeline-sls-entitlement, pipeline-certificates, pipeline-pod-templates, pipeline-aiservice-config, - pipeline-db2-license, and pipeline-facilities-properties. + Creates secrets in the specified namespace: mas-devops-slack, {registry_secret_name}, + pipeline-additional-configs, pipeline-sls-entitlement, pipeline-certificates, + pipeline-pod-templates, pipeline-aiservice-config, pipeline-db2-license, and + pipeline-facilities-properties. Parameters: dynClient (DynamicClient): OpenShift Dynamic Client @@ -913,6 +962,9 @@ def prepareInstallSecrets( slack_channel (str, optional): Slack channel ID for notifications. Defaults to None. aiserviceConfig (str, optional): AI Service tenant config data. Defaults to None (empty secret). facilitiesProperties (dict, optional): Facilities properties file content. Defaults to None (empty secret). + ibm_entitlement_key (str, optional): IBM entitlement key for registry access. Defaults to None. + artifactory_token (str, optional): Artifactory token for dev catalog access. Defaults to None. + artifactory_username (str, optional): Artifactory username for dev catalog access. Defaults to None. Returns: None @@ -959,6 +1011,46 @@ def prepareInstallSecrets( secretsAPI.create(body=mas_devops_secret, namespace=namespace) logger.info(f"Created mas-devops-slack secret with MAS_INSTANCE_ID={instance_id} in namespace {namespace}") + # 1. Secret/{registry_secret_name} + # ------------------------------------------------------------------------- + # Per-pipeline secret holding registry credentials sourced from secret instead of pipeline params. + # Only created when at least one credential is provided — all keys are optional. + # Secret name is derived from namespace prefix if not explicitly provided: + # mas-{id}-pipelines → mas-install-secrets + # aiservice-{id}-pipelines → mas-aiservice-install-secrets + if instance_id: + if registry_secret_name is None: + registry_secret_name = "mas-aiservice-install-secrets" if namespace.startswith("aiservice-") else "mas-install-secrets" + + credentials_data = {} + + if ibm_entitlement_key: + credentials_data["IBM_ENTITLEMENT_KEY"] = base64.b64encode(ibm_entitlement_key.encode()).decode() + + if artifactory_token: + credentials_data["ARTIFACTORY_TOKEN"] = base64.b64encode(artifactory_token.encode()).decode() + + if artifactory_username: + credentials_data["ARTIFACTORY_USERNAME"] = base64.b64encode(artifactory_username.encode()).decode() + + if credentials_data: + try: + secretsAPI.delete(name=registry_secret_name, namespace=namespace) + except NotFoundError: + pass + + secretsAPI.create( + body={ + "apiVersion": "v1", + "kind": "Secret", + "type": "Opaque", + "metadata": {"name": registry_secret_name}, + "data": credentials_data, + }, + namespace=namespace, + ) + logger.info(f"Created {registry_secret_name} secret in namespace {namespace}") + # 1. Secret/pipeline-additional-configs # ------------------------------------------------------------------------- # Must exist, but can be empty @@ -1074,17 +1166,24 @@ def prepareUpdateSecrets( slack_token: str = None, slack_channel: str = None, db2LicenseFile: dict | None = None, + artifactory_token: str = None, + artifactory_username: str = None, + registry_secret_name: str = "mas-update-secrets", ) -> None: """ Create or update mas-devops-slack secret in mas-pipelines namespace for update pipeline. Creates the slack secret in mas-pipelines namespace if it exists and slack credentials are provided. + Also creates {registry_secret_name} secret if artifactory credentials are provided. Parameters: dynClient (DynamicClient): OpenShift Dynamic Client slack_token (str, optional): Slack bot token for notifications. Defaults to None. slack_channel (str, optional): Slack channel ID for notifications. Defaults to None. db2LicenseFile (dict, optional): Db2 license file content. Defaults to None (empty secret). + artifactory_token (str, optional): Artifactory token for dev catalog access. Defaults to None. + artifactory_username (str, optional): Artifactory username for dev catalog access. Defaults to None. + registry_secret_name (str, optional): Name of the per-pipeline registry credentials secret. Defaults to "mas-update-secrets". Returns: None @@ -1152,6 +1251,95 @@ def prepareUpdateSecrets( secretsAPI.create(body=mas_devops_secret, namespace=namespace) logger.info(f"Created mas-devops-slack secret in namespace {namespace}") + # Create {registry_secret_name} if artifactory credentials are provided + # Note: update pipeline does not use ibm_entitlement_key (skipped via skip_entitlement_key_flag) + credentials_data = {} + + if artifactory_token: + credentials_data["ARTIFACTORY_TOKEN"] = base64.b64encode(artifactory_token.encode()).decode() + + if artifactory_username: + credentials_data["ARTIFACTORY_USERNAME"] = base64.b64encode(artifactory_username.encode()).decode() + + if credentials_data: + try: + secretsAPI.delete(name=registry_secret_name, namespace=namespace) + except NotFoundError: + pass + + secretsAPI.create( + body={ + "apiVersion": "v1", + "kind": "Secret", + "type": "Opaque", + "metadata": {"name": registry_secret_name}, + "data": credentials_data, + }, + namespace=namespace, + ) + logger.info(f"Created {registry_secret_name} secret in namespace {namespace}") + + +def prepareUpgradeSecrets( + dynClient: DynamicClient, + namespace: str, + ibm_entitlement_key: str = None, + artifactory_token: str = None, + artifactory_username: str = None, + registry_secret_name: str = "mas-upgrade-secrets", +) -> None: + """ + Create the registry credentials secret required for the MAS Upgrade pipeline. + + Upgrade tasks pull images from ICR (ibm_entitlement_key) and optionally from + Artifactory (artifactory_token / artifactory_username). Credentials are written + into a named OCP Secret so they are never visible as plaintext PipelineRun params. + + Only keys with non-empty values are written to the secret. + The secret is skipped entirely if no credentials are provided. + + Parameters: + dynClient (DynamicClient): OpenShift Dynamic Client + namespace (str): The pipeline namespace (mas-{instanceId}-pipelines) + ibm_entitlement_key (str, optional): IBM entitlement key for ICR image pulls. Defaults to None. + artifactory_token (str, optional): Artifactory token for dev catalog access. Defaults to None. + artifactory_username (str, optional): Artifactory username for dev catalog access. Defaults to None. + registry_secret_name (str, optional): Name of the secret to create. Defaults to "mas-upgrade-secrets". + + Returns: + None + """ + secretsAPI = dynClient.resources.get(api_version="v1", kind="Secret") + + credentials_data = {} + + if ibm_entitlement_key: + credentials_data["IBM_ENTITLEMENT_KEY"] = base64.b64encode(ibm_entitlement_key.encode()).decode() + + if artifactory_token: + credentials_data["ARTIFACTORY_TOKEN"] = base64.b64encode(artifactory_token.encode()).decode() + + if artifactory_username: + credentials_data["ARTIFACTORY_USERNAME"] = base64.b64encode(artifactory_username.encode()).decode() + + if credentials_data: + try: + secretsAPI.delete(name=registry_secret_name, namespace=namespace) + except NotFoundError: + pass + + secretsAPI.create( + body={ + "apiVersion": "v1", + "kind": "Secret", + "type": "Opaque", + "metadata": {"name": registry_secret_name}, + "data": credentials_data, + }, + namespace=namespace, + ) + logger.info(f"Created {registry_secret_name} secret in namespace {namespace}") + def testCLI() -> None: pass @@ -1363,6 +1551,63 @@ def launchUpdatePipeline(dynClient: DynamicClient, params: dict) -> str: return pipelineURL +def prepareBackupSecrets( + dynClient: DynamicClient, + namespace: str, + artifactory_token: str = None, + artifactory_username: str = None, + registry_secret_name: str = "mas-backup-secrets", +) -> None: + """ + Create the registry credentials secret required for the MAS Backup pipeline. + + Backup tasks do not use ibm_entitlement_key (no image pulls from ICR), but may + use Artifactory credentials to upload backup archives to an Artifactory repository. + Credentials are written into a named OCP Secret so they are never visible as + plaintext PipelineRun params. + + Only keys with non-empty values are written to the secret. + The secret is skipped entirely if no credentials are provided. + + Parameters: + dynClient (DynamicClient): OpenShift Dynamic Client + namespace (str): The pipeline namespace (mas-{instanceId}-pipelines) + artifactory_token (str, optional): Artifactory token for archive upload. Defaults to None. + artifactory_username (str, optional): Artifactory username for archive upload. Defaults to None. + registry_secret_name (str, optional): Name of the secret to create. Defaults to "mas-backup-secrets". + + Returns: + None + """ + secretsAPI = dynClient.resources.get(api_version="v1", kind="Secret") + + credentials_data = {} + + if artifactory_token: + credentials_data["ARTIFACTORY_TOKEN"] = base64.b64encode(artifactory_token.encode()).decode() + + if artifactory_username: + credentials_data["ARTIFACTORY_USERNAME"] = base64.b64encode(artifactory_username.encode()).decode() + + if credentials_data: + try: + secretsAPI.delete(name=registry_secret_name, namespace=namespace) + except NotFoundError: + pass + + secretsAPI.create( + body={ + "apiVersion": "v1", + "kind": "Secret", + "type": "Opaque", + "metadata": {"name": registry_secret_name}, + "data": credentials_data, + }, + namespace=namespace, + ) + logger.info(f"Created {registry_secret_name} secret in namespace {namespace}") + + def launchBackupPipeline(dynClient: DynamicClient, params: dict) -> str: """ Create a PipelineRun to backup a MAS instance. @@ -1450,6 +1695,68 @@ def launchAiServiceUpgradePipeline( return pipelineURL +def prepareAiServiceUpgradeSecrets( + dynClient: DynamicClient, + namespace: str, + ibm_entitlement_key: str = None, + artifactory_token: str = None, + artifactory_username: str = None, + registry_secret_name: str = "mas-aiservice-upgrade-secrets", +) -> None: + """ + Create the registry credentials secret required for the AI Service Upgrade pipeline. + + The aiservice_upgrade Ansible role uses ibm_entitlement_key (ICR image pulls) and + optionally artifactory_token / artifactory_username (dev catalog access). + Credentials are written into a named OCP Secret so they are never visible as + plaintext PipelineRun params. + + Only keys with non-empty values are written to the secret. + The secret is skipped entirely if no credentials are provided. + + Parameters: + dynClient (DynamicClient): OpenShift Dynamic Client + namespace (str): The pipeline namespace (aiservice-{instanceId}-pipelines) + ibm_entitlement_key (str, optional): IBM entitlement key for ICR image pulls. Defaults to None. + artifactory_token (str, optional): Artifactory token for dev catalog access. Defaults to None. + artifactory_username (str, optional): Artifactory username for dev catalog access. Defaults to None. + registry_secret_name (str, optional): Name of the secret to create. Defaults to "mas-aiservice-upgrade-secrets". + + Returns: + None + """ + secretsAPI = dynClient.resources.get(api_version="v1", kind="Secret") + + credentials_data = {} + + if ibm_entitlement_key: + credentials_data["IBM_ENTITLEMENT_KEY"] = base64.b64encode(ibm_entitlement_key.encode()).decode() + + if artifactory_token: + credentials_data["ARTIFACTORY_TOKEN"] = base64.b64encode(artifactory_token.encode()).decode() + + if artifactory_username: + credentials_data["ARTIFACTORY_USERNAME"] = base64.b64encode(artifactory_username.encode()).decode() + + if credentials_data: + try: + secretsAPI.delete(name=registry_secret_name, namespace=namespace) + except NotFoundError: + pass + + secretsAPI.create( + body={ + "apiVersion": "v1", + "kind": "Secret", + "type": "Opaque", + "metadata": {"name": registry_secret_name}, + "data": credentials_data, + }, + namespace=namespace, + ) + logger.info(f"Created {registry_secret_name} secret in namespace {namespace}") + + def prepareInstallRBAC(dynClient: DynamicClient, namespace: str, instanceId: str, installRBACDir: str) -> None: """ Apply the minimal install RBAC bundle for a MAS instance. diff --git a/src/mas/devops/templates/pipelinerun-aiservice-upgrade.yml.j2 b/src/mas/devops/templates/pipelinerun-aiservice-upgrade.yml.j2 index 4b5bc70c..d340ce8d 100644 --- a/src/mas/devops/templates/pipelinerun-aiservice-upgrade.yml.j2 +++ b/src/mas/devops/templates/pipelinerun-aiservice-upgrade.yml.j2 @@ -15,6 +15,11 @@ spec: pipeline: "0" params: + # Registry Credentials Secret + # ------------------------------------------------------------------------- + - name: pipeline_registry_secret_name + value: "mas-aiservice-upgrade-secrets" + # Target AI Service Instance # ------------------------------------------------------------------------- - name: aiservice_instance_id @@ -22,27 +27,12 @@ spec: - 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 # ------------------------------------------------------------------------- diff --git a/src/mas/devops/templates/pipelinerun-backup.yml.j2 b/src/mas/devops/templates/pipelinerun-backup.yml.j2 index d5386710..3019ef70 100644 --- a/src/mas/devops/templates/pipelinerun-backup.yml.j2 +++ b/src/mas/devops/templates/pipelinerun-backup.yml.j2 @@ -17,6 +17,11 @@ spec: persistentVolumeClaim: claimName: backup-pvc params: + # Registry Credentials Secret + # ------------------------------------------------------------------------- + - name: pipeline_registry_secret_name + value: "mas-backup-secrets" + # Common Parameters - name: image_pull_policy value: IfNotPresent @@ -74,16 +79,6 @@ spec: value: "{{ cert_manager_provider }}" {% endif %} - # Development Build Support - {% if artifactory_username is defined and artifactory_username != "" %} - - name: artifactory_username - value: "{{ artifactory_username }}" - {% endif %} - {% if artifactory_token is defined and artifactory_token != "" %} - - name: artifactory_token - value: "{{ artifactory_token }}" - {% endif %} - # Upload Configuration {% if upload_backup is defined and upload_backup != "" %} - name: upload_backup diff --git a/src/mas/devops/templates/pipelinerun-install.yml.j2 b/src/mas/devops/templates/pipelinerun-install.yml.j2 index ca6dd277..eb422e8e 100644 --- a/src/mas/devops/templates/pipelinerun-install.yml.j2 +++ b/src/mas/devops/templates/pipelinerun-install.yml.j2 @@ -19,10 +19,15 @@ spec: pipeline: "0" params: - # IBM Entitlement Key + # Registry Credentials Secret # ------------------------------------------------------------------------- - - name: ibm_entitlement_key - value: "{{ ibm_entitlement_key }}" + - name: pipeline_registry_secret_name +{%- if mas_instance_id is defined and mas_instance_id != "" %} + value: "mas-install-secrets" +{%- else %} + value: "mas-aiservice-install-secrets" +{%- endif %} + {%- if skip_pre_check is defined and skip_pre_check != "" %} # Pipeline config @@ -44,15 +49,6 @@ spec: - name: ocp_ingress_tls_secret_name value: "{{ ocp_ingress_tls_secret_name }}" {%- 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 %} {%- if ibmcloud_apikey is defined and ibmcloud_resourcegroup != "" %} # IBM Cloud diff --git a/src/mas/devops/templates/pipelinerun-restore.yml.j2 b/src/mas/devops/templates/pipelinerun-restore.yml.j2 index bf5ae6fa..10654533 100644 --- a/src/mas/devops/templates/pipelinerun-restore.yml.j2 +++ b/src/mas/devops/templates/pipelinerun-restore.yml.j2 @@ -20,6 +20,11 @@ spec: secret: secretName: pipeline-restore-configs params: + # Registry Credentials Secret + # ------------------------------------------------------------------------- + - name: pipeline_registry_secret_name + value: "mas-restore-secrets" + # Common Parameters - name: image_pull_policy value: IfNotPresent @@ -99,10 +104,6 @@ spec: - name: dro_contact_lastname value: "{{ dro_contact_lastname }}" {% endif %} - {% if ibm_entitlement_key is defined and ibm_entitlement_key != "" %} - - name: ibm_entitlement_key - value: "{{ ibm_entitlement_key }}" - {% endif %} {% if dro_namespace is defined and dro_namespace != "" %} - name: dro_namespace value: "{{ dro_namespace }}" @@ -148,16 +149,6 @@ spec: value: "{{ cert_manager_provider }}" {% endif %} - # Development Build Support - {% if artifactory_username is defined and artifactory_username != "" %} - - name: artifactory_username - value: "{{ artifactory_username }}" - {% endif %} - {% if artifactory_token is defined and artifactory_token != "" %} - - name: artifactory_token - value: "{{ artifactory_token }}" - {% endif %} - # Download Configuration {% if backup_archive_name is defined and backup_archive_name != "" %} - name: backup_archive_name diff --git a/src/mas/devops/templates/pipelinerun-update.yml.j2 b/src/mas/devops/templates/pipelinerun-update.yml.j2 index 1370379e..2886d644 100644 --- a/src/mas/devops/templates/pipelinerun-update.yml.j2 +++ b/src/mas/devops/templates/pipelinerun-update.yml.j2 @@ -15,6 +15,11 @@ spec: pipeline: "0" params: + # Registry Credentials Secret + # ------------------------------------------------------------------------- + - name: pipeline_registry_secret_name + value: "mas-update-secrets" + {%- if image_pull_policy is defined and image_pull_policy != "" %} # Image Pull Policy @@ -28,19 +33,6 @@ spec: - name: mas_catalog_version value: "{{ mas_catalog_version }}" -{%- if ibm_entitlement_key is defined and ibm_entitlement_key != "" %} - # TODO: What even uses this, nothing in the update pipeline should be using this - - name: ibm_entitlement_key - value: "{{ ibm_entitlement_key }}" -{%- 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 %} {%- if skip_pre_check is defined and skip_pre_check != "" %} # Skip pre-check # ------------------------------------------------------------------------- diff --git a/src/mas/devops/templates/pipelinerun-upgrade.yml.j2 b/src/mas/devops/templates/pipelinerun-upgrade.yml.j2 index 2d451ab9..11780e1e 100644 --- a/src/mas/devops/templates/pipelinerun-upgrade.yml.j2 +++ b/src/mas/devops/templates/pipelinerun-upgrade.yml.j2 @@ -15,6 +15,11 @@ spec: pipeline: "0" params: + # Registry Credentials Secret + # ------------------------------------------------------------------------- + - name: pipeline_registry_secret_name + value: "mas-upgrade-secrets" + {%- if image_pull_policy is defined and image_pull_policy != "" %} # Image Pull Policy @@ -30,26 +35,12 @@ spec: - name: mas_channel value: "{{ mas_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 %} {%- if storage_class_rwo is defined and storage_class_rwo != "" %} - name: storage_class_rwo value: "{{ storage_class_rwo }}"