diff --git a/instance-applications/113-ibm-aiservice/templates/03-aiservice-jdbc.yaml b/instance-applications/113-ibm-aiservice/templates/03-aiservice-jdbc.yaml index f55b72e79..3db04cbb5 100644 --- a/instance-applications/113-ibm-aiservice/templates/03-aiservice-jdbc.yaml +++ b/instance-applications/113-ibm-aiservice/templates/03-aiservice-jdbc.yaml @@ -12,6 +12,6 @@ data: password: {{ .Values.jdbccfg_password | default "" | toString | b64enc | quote }} url: {{ .Values.jdbccfg_url | default "" | toString | b64enc | quote }} sslenabled: {{ .Values.jdbccfg_sslenabled | default "" | toString | b64enc | quote }} - certificate: {{ .Values.jdbccfg_ca_b64enc }} + certificate: {{ .Values.jdbccfg_ca_b64enc | default "" | toString | quote }} type: Opaque {{- end }} diff --git a/instance-applications/113-ibm-aiservice/values.yaml b/instance-applications/113-ibm-aiservice/values.yaml index f87bc996a..d90c02449 100644 --- a/instance-applications/113-ibm-aiservice/values.yaml +++ b/instance-applications/113-ibm-aiservice/values.yaml @@ -76,6 +76,7 @@ mas_icr_cpopen: "icr.io/cpopen" cluster_domain: "_cluster_dns.resources[0].spec.baseDomain" in_saas_env: "false" +is_gitops_fvt_env: "false" #TENANT mas_aiservice_tenant_name: "aiservice-{{ mas_aiservice_tenant_name }}" \ No newline at end of file diff --git a/instance-applications/115-ibm-aiservice-tenant/README.md b/instance-applications/115-ibm-aiservice-tenant/README.md index 3544079dc..03fd98748 100644 --- a/instance-applications/115-ibm-aiservice-tenant/README.md +++ b/instance-applications/115-ibm-aiservice-tenant/README.md @@ -20,6 +20,8 @@ This chart provisions a tenant for Maximo AI Service. It installs the AI Service | `ServiceAccount` | Migration and post-sync service accounts | Tenant namespace | Always | `application_admin_role` | | `Role` | Migration and post-sync roles | Tenant namespace | Always | `application_admin_role` | | `RoleBinding` | Migration and post-sync role bindings | Tenant namespace | Always | `application_admin_role` | +| `ClusterRole` | Tenant cluster-level permissions | Cluster-wide | Always | `application_admin_role` | +| `ClusterRoleBinding` | Tenant cluster role bindings | Cluster-wide | Always | `application_admin_role` | | `NetworkPolicy` | Tenant migration and ingress network policies | Tenant namespace | Always | `application_admin_role` | | `Job` | Migration, post-sync, and secret setup jobs | Tenant namespace | Always | `application_admin_role` | diff --git a/instance-applications/115-ibm-aiservice-tenant/templates/08-aiservice-postsyncjob.yaml b/instance-applications/115-ibm-aiservice-tenant/templates/08-aiservice-postsyncjob.yaml index 7fffffcef..0fd7ac8c1 100644 --- a/instance-applications/115-ibm-aiservice-tenant/templates/08-aiservice-postsyncjob.yaml +++ b/instance-applications/115-ibm-aiservice-tenant/templates/08-aiservice-postsyncjob.yaml @@ -170,9 +170,33 @@ spec: echo "Retrieve AIBroker API Key for tenant: ${AISERVICE_TENANT}" echo "================================================================================" - AISERVICE_APIKEY_SECRET=$(oc get secret ${AISERVICE_TENANT}----apikey-secret -n ${TARGET_NAMESPACE} -o jsonpath="{.data.AIBROKER_APIKEY}" | base64 --decode) + # Wait for the apikey-secret to be created by the AIService operator + # The operator creates this secret after the AIServiceTenant CR is reconciled + SECRET_NAME="${AISERVICE_TENANT}----apikey-secret" + MAX_RETRIES=30 + RETRY_DELAY=10 + + echo "Waiting for secret ${SECRET_NAME} to be created in namespace ${TARGET_NAMESPACE}..." + for i in $(seq 1 $MAX_RETRIES); do + if oc get secret ${SECRET_NAME} -n ${TARGET_NAMESPACE} > /dev/null 2>&1; then + echo "Secret ${SECRET_NAME} found (attempt $i/$MAX_RETRIES)" + break + fi + + if [ $i -eq $MAX_RETRIES ]; then + echo "ERROR: Secret ${SECRET_NAME} not found after ${MAX_RETRIES} attempts" + echo "The AIService operator may not have created the secret yet" + exit 1 + fi + + echo "Secret not found yet, waiting ${RETRY_DELAY}s... (attempt $i/$MAX_RETRIES)" + sleep $RETRY_DELAY + done + + AISERVICE_APIKEY_SECRET=$(oc get secret ${SECRET_NAME} -n ${TARGET_NAMESPACE} -o jsonpath="{.data.AIBROKER_APIKEY}" | base64 --decode) if [ -z "$AISERVICE_APIKEY_SECRET" ]; then - echo "AISERVICE_APIKEY_SECRET is empty" + echo "ERROR: AISERVICE_APIKEY_SECRET is empty in secret ${SECRET_NAME}" + echo "The secret exists but does not contain the AIBROKER_APIKEY field" exit 1 fi diff --git a/instance-applications/115-ibm-aiservice-tenant/templates/98-presync-copy-sls.yaml b/instance-applications/115-ibm-aiservice-tenant/templates/98-presync-copy-sls.yaml new file mode 100644 index 000000000..347f03d91 --- /dev/null +++ b/instance-applications/115-ibm-aiservice-tenant/templates/98-presync-copy-sls.yaml @@ -0,0 +1,382 @@ +{{- /* TEMPORARY: Condition removed for testing +{{- if eq .Values.ibm_aiservice_tenant.is_gitops_fvt_env "true" }} +*/}} + +{{- /* +Meaningful prefix for the job resource name. Must be under 52 chars in length to leave room for the 11 chars reserved for '-' and $_job_hash. +*/}} +{{- $_job_name_prefix := "presync-copy-sls-to-tenant-job" }} + +{{- /* +Use the build/bin/set-cli-image-digest.sh script to update this value across all charts. +Included in $_job_hash (see below). +*/}} +{{- $_cli_image_digest := "sha256:4636b74525a46ebd88cd540794e8e23143f0112ea85149f9dfc78d02704ad5a6" }} + +{{- /* +A dict of values that influence the behaviour of the job in some way. +Any changes to values in this dict will trigger a rerun of the job. +Since jobs must be idemopotent, it's generally safe to pass in values here that are not +strictly necessary (i.e. including some values that don't actually influence job behaviour). +We may want to refine this further though for jobs that can take a long time to complete. +Included in $_job_hash (see below). +*/}} +{{- $_job_config_values := omit .Values "junitreporter" }} + +{{- /* +Increment this value whenever you make a change to an immutable field of the Job resource. +E.g. passing in a new environment variable. +Included in $_job_hash (see below). +*/}} +{{- $_job_version := "v4" }} + +{{- /* +10 char hash appended to the job name taking into account $_job_config_values, $_job_version and $_cli_image_digest +This is to ensure ArgoCD will create a new job resource intead of attempting (and failing) to update an +immutable field of any existing Job resource. +*/}} +{{- $_job_hash := print ($_job_config_values | toYaml) $_cli_image_digest $_job_version | adler32sum }} + +{{- $_job_name := join "-" (list $_job_name_prefix $_job_hash )}} + +{{- /* +Set as the value for the mas.ibm.com/job-cleanup-group label on the Job resource. + +When the auto_delete flag is not set on the root application, a CronJob in the cluster uses this label +to identify old Job resources that should be pruned on behalf of ArgoCD. + +Any Job resources in the same namespace that have the mas.ibm.com/job-cleanup-group with this value +will be considered to belong to the same cleanup group. All but the most recent (i.e. with the latest "creation_timestamp") +Jobs will be automatically deleted. + +$_job_cleanup_group can usually just be based on $_job_name_prefix. There are some special cases +where multiple Jobs are created in our templates using a Helm loop. In those cases, additional descriminators +must be added to $_job_cleanup_group. + +By convention, we sha1sum this value to guarantee we never exceed the 63 char limit regardless of which discriminators +are required here. + +*/}} +{{- $_job_cleanup_group := cat $_job_name_prefix | sha1sum }} + +{{- /* +ICN and subscription_id will be extracted from the resolved slscfg_url at runtime. +Deploy the Job in the tenant namespace (which already exists). +The Job will access the SLS namespace to read ConfigMaps. +*/}} + +{{ $ns := .Values.tenantNamespace }} +{{ $instance := .Values.tenant_id }} + +{{ $aws_secret := "aws"}} +{{ $np_name := "postsync-ibm-sls-update-sm-np" }} +{{ $role_name := "postsync-ibm-sls-update-sm-r" }} +{{ $sa_name := "postsync-ibm-sls-update-sm-sa" }} +{{ $rb_name := "postsync-ibm-sls-update-sm-rb" }} +{{ $job_label := "postsync-ibm-sls-update-sm-job" }} + + + +--- +# Permit outbound communication by the Job pods +# (Needed to communicate with the K8S HTTP API and AWS SM) +kind: NetworkPolicy +apiVersion: networking.k8s.io/v1 +metadata: + name: {{ $np_name }} + namespace: {{ $ns }} + annotations: + argocd.argoproj.io/sync-wave: "98" +{{- if .Values.custom_labels }} + labels: +{{ .Values.custom_labels | toYaml | indent 4 }} +{{- end }} +spec: + podSelector: + matchLabels: + app: {{ $job_label }} + egress: + - {} + policyTypes: + - Egress + + +--- +kind: Secret +apiVersion: v1 +metadata: + name: {{ $aws_secret }} + namespace: {{ $ns }} + annotations: + argocd.argoproj.io/sync-wave: "98" +{{- if .Values.custom_labels }} + labels: +{{ .Values.custom_labels | toYaml | indent 4 }} +{{- end }} +data: + aws_access_key_id: {{ .Values.sm_aws_access_key_id | default "" | b64enc }} + aws_secret_access_key: {{ .Values.sm_aws_secret_access_key | default "" | b64enc }} +type: Opaque + +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + name: {{ $sa_name }} + namespace: {{ $ns }} + annotations: + argocd.argoproj.io/sync-wave: "98" +{{- if .Values.custom_labels }} + labels: +{{ .Values.custom_labels | toYaml | indent 4 }} +{{- end }} + +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ $role_name }}-{{ $instance }} + annotations: + argocd.argoproj.io/sync-wave: "98" +{{- if .Values.custom_labels }} + labels: +{{ .Values.custom_labels | toYaml | indent 4 }} +{{- end }} +rules: + - verbs: + - get + - list + apiGroups: + - "" + resources: + - configmaps + - namespaces + - verbs: + - get + - list + - patch + apiGroups: + - "route.openshift.io" + resources: + - routes + +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ $rb_name }}-{{ $instance }} + annotations: + argocd.argoproj.io/sync-wave: "98" +{{- if .Values.custom_labels }} + labels: +{{ .Values.custom_labels | toYaml | indent 4 }} +{{- end }} +subjects: + - kind: ServiceAccount + name: {{ $sa_name }} + namespace: {{ $ns }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ $role_name }}-{{ $instance }} + +--- +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ $_job_name }} + namespace: {{ $ns }} + annotations: + argocd.argoproj.io/sync-wave: "98" + argocd.argoproj.io/sync-options: Prune=true + labels: + mas.ibm.com/job-cleanup-group: {{ $_job_cleanup_group }} +{{- if .Values.custom_labels }} +{{ .Values.custom_labels | toYaml | indent 4 }} +{{- end }} +spec: + template: + metadata: + labels: + app: {{ $job_label }} +{{- if .Values.custom_labels }} +{{ .Values.custom_labels | toYaml | indent 8 }} +{{- end }} + spec: + containers: + - name: run + image: quay.io/ibmmas/cli@{{ $_cli_image_digest }} + imagePullPolicy: IfNotPresent + resources: + limits: + cpu: 200m + memory: 512Mi + requests: + cpu: 10m + memory: 64Mi + env: + - name: ACCOUNT_ID + value: {{ .Values.account_id }} + - name: REGION_ID + value: {{ .Values.region_id }} + {{- if .Values.cluster_url }} + - name: CLUSTER_URL + value: {{ .Values.cluster_url }} + {{- end }} + - name: SLSCFG_URL + value: '{{ .Values.slscfg_url }}' + {{- if .Values.ibm_customer_number }} + - name: ICN + value: '{{ .Values.ibm_customer_number }}' + {{- end }} + {{- if .Values.aiservice_sls_subscription_id }} + - name: SUBSCRIPTION_ID + value: '{{ .Values.aiservice_sls_subscription_id }}' + {{- end }} + - name: TENANT + value: '{{ .Values.tenant_id }}' + - name: TENANT_NAMESPACE + value: '{{ .Values.tenantNamespace }}' + + # Hard-coded for now: + - name: AVP_TYPE + value: "aws" + volumeMounts: + - name: aws + mountPath: /etc/mas/creds/aws + command: + - /bin/sh + - -c + - | + + set -e + + # Extract ICN, SUBSCRIPTION_ID, and DOMAIN from SLSCFG_URL if not provided + # SLSCFG_URL format: https://sls.mas-87654350-bedd3ceac250-sls.apps.fvtsaasai.2rdp.p1.openshiftapps.com + if [[ -z "${ICN}" || -z "${SUBSCRIPTION_ID}" ]]; then + echo "Extracting ICN and SUBSCRIPTION_ID from SLSCFG_URL: ${SLSCFG_URL}" + # Extract the namespace part: mas-87654350-bedd3ceac250-sls + SLS_NAMESPACE=$(echo "${SLSCFG_URL}" | sed -n 's|https://sls\.\(mas-[^.]*-sls\)\..*|\1|p') + if [[ -z "${SLS_NAMESPACE}" ]]; then + echo "ERROR: Failed to extract SLS namespace from SLSCFG_URL" + exit 1 + fi + echo "Extracted SLS namespace: ${SLS_NAMESPACE}" + + # Extract ICN and SUBSCRIPTION_ID from namespace: mas-ICN-SUBID-sls + ICN=$(echo "${SLS_NAMESPACE}" | cut -d'-' -f2) + SUBSCRIPTION_ID=$(echo "${SLS_NAMESPACE}" | cut -d'-' -f3) + echo "Extracted ICN: ${ICN}, SUBSCRIPTION_ID: ${SUBSCRIPTION_ID}" + fi + + # Extract DOMAIN from CLUSTER_URL or SLSCFG_URL + if [[ -n "${CLUSTER_URL}" ]]; then + DOMAIN=$(echo "${CLUSTER_URL}" | sed -n 's|https://api\.[^.]*\.\(.*\):.*|\1|p') + else + DOMAIN=$(echo "${SLSCFG_URL}" | sed -n 's|https://[^.]*\.\(.*\)|\1|p') + fi + echo "Using DOMAIN: ${DOMAIN}" + + # NOTE: cannot just render AWS secrets into here, as it will be exposed in the ArgoCD UI + # Instead, we pass them into a secret (ArgoCD knows to hide any data fields in k8s secrets), + # mount the secret on the jobs filesystem, and read them in here + SM_AWS_ACCESS_KEY_ID=$(cat /etc/mas/creds/aws/aws_access_key_id) + SM_AWS_SECRET_ACCESS_KEY=$(cat /etc/mas/creds/aws/aws_secret_access_key) + + # Fetch SLS data from ConfigMap in the SLS namespace using kubectl + SLS_NAMESPACE="mas-${ICN}-${SUBSCRIPTION_ID}-sls" + echo "Fetching registrationKey from sls-suite-registration ConfigMap in ${SLS_NAMESPACE}" + SLS_REGISTRATION_KEY=$(kubectl get configmap sls-suite-registration -n ${SLS_NAMESPACE} -o jsonpath='{.data.registrationKey}' 2>/dev/null) + if [[ -z "${SLS_REGISTRATION_KEY}" ]]; then + echo "Failed to fetch registrationKey from ${SLS_NAMESPACE}" + exit 1 + fi + + echo "Fetching ca from sls-suite-registration ConfigMap in ${SLS_NAMESPACE}" + SLS_CA=$(kubectl get configmap sls-suite-registration -n ${SLS_NAMESPACE} -o jsonpath='{.data.ca}' 2>/dev/null | base64 -w0) + if [[ -z "${SLS_CA}" ]]; then + echo "Failed to fetch ca from ${SLS_NAMESPACE}" + exit 1 + fi + + echo "Setting SLS URL" + SLS_URL="https://sls.mas-${ICN}-${SUBSCRIPTION_ID}-sls.${DOMAIN}" + + # might as well take advantage of gitops_utils for sm_ functions as we're using the cli image + source /mascli/functions/gitops_utils + + # aws configure set aws_access_key_id $SM_AWS_ACCESS_KEY_ID + # aws configure set aws_secret_access_key $SM_AWS_SECRET_ACCESS_KEY + # aws configure set default.region $REGION_ID + # aws configure list + export SM_AWS_REGION=${REGION_ID} + sm_login + + # aws secretsmanager create-secret --name ${SECRET_NAME} --secret-string "${SECRET_VALUE}" + ## Why is the secreet name here sls and not license as it is in gitops_license function + SECRET_NAME_SLS=${ACCOUNT_ID}/${ICN}/${SUBSCRIPTION_ID}/sls + # TAGS="[{\"Key\": \"source\", \"Value\": \"postsync-ibm-sls-update-sm-job\"}, {\"Key\": \"account\", \"Value\": \"${ACCOUNT_ID}\"}, {\"Key\": \"subscription_id\", \"Value\": \"${SUBSCRIPTION_ID}\"}]" + # sm_update_secret $SECRET_NAME_SLS "{\"registration_key\": \"$SLS_REGISTRATION_KEY\", \"ca_b64\": \"$SLS_CA\", \"sls_url\":\"$SLS_URL\" }" "${TAGS}" + + # Fetch the instance-level SLS secret and create tenant-level copy + echo "" + echo "Fetching instance-level SLS secret from AWS Secrets Manager" + RAW_SLS_SECRET=$(aws secretsmanager get-secret-value --secret-id "$SECRET_NAME_SLS" --output json 2>/dev/null | jq -r .SecretString) + + if [[ -n "$RAW_SLS_SECRET" && "$RAW_SLS_SECRET" != "null" ]]; then + echo "Successfully fetched instance-level SLS secret" + + # Create tenant-level secret using TENANT variable (contains tenant ID) + if [[ -n "${TENANT:-}" ]]; then + # Create tenant-level secret path + SECRET_NAME_TENANT_SLS=${ACCOUNT_ID}/${ICN}/${SUBSCRIPTION_ID}/${TENANT}/sls + echo "Creating tenant-level SLS secret at: ${SECRET_NAME_TENANT_SLS}" + + TENANT_TAGS="[{\"Key\": \"source\", \"Value\": \"presync-copy-sls-to-tenant-job\"}, {\"Key\": \"account\", \"Value\": \"${ACCOUNT_ID}\"}, {\"Key\": \"subscription_id\", \"Value\": \"${SUBSCRIPTION_ID}\"}, {\"Key\": \"tenant_id\", \"Value\": \"${TENANT}\"}]" + + # Use sm_update_secret to create/update tenant-level secret with same content + sm_update_secret $SECRET_NAME_TENANT_SLS "$RAW_SLS_SECRET" "${TENANT_TAGS}" + + echo "Successfully created/updated tenant-level SLS secret" + else + echo "TENANT variable not set, skipping tenant-level secret creation" + fi + else + echo "Failed to fetch instance-level SLS secret, skipping tenant-level secret creation" + fi + + # 1. Define the namespace using the environment variables passed to the container + namespace="mas-${ICN}-${SUBSCRIPTION_ID}-sls" + + echo "Fetching routes from ${namespace}" + + routes=$(oc get routes -n ${namespace} -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}') + + echo "Routes found:" + echo "${routes}" + + for route in $routes; do + echo "Adding label to route - ${route}" + + oc patch route ${route} \ + -n ${namespace} \ + --type=merge \ + -p '{"metadata":{"labels":{"type":"external"}}}' + done + + restartPolicy: Never + + # TODO: is this the correct SA to use here? + # No, probably want to add a more restricted SA that can just do things that these post-sync jobs need to do + serviceAccountName: {{ $sa_name }} + volumes: + - name: aws + secret: + secretName: {{ $aws_secret }} + defaultMode: 420 + optional: false + + backoffLimit: 4 +{{- /* TEMPORARY: End condition commented out for testing +{{- end }} +*/}} \ No newline at end of file diff --git a/instance-applications/115-ibm-aiservice-tenant/values.yaml b/instance-applications/115-ibm-aiservice-tenant/values.yaml index 578b26a1a..aa9262860 100644 --- a/instance-applications/115-ibm-aiservice-tenant/values.yaml +++ b/instance-applications/115-ibm-aiservice-tenant/values.yaml @@ -1,6 +1,10 @@ catalog_channel: "9.2.x-dev" catalog_source: ibm-operator-catalog +# FVT Environment Configuration +ibm_aiservice_tenant: + is_gitops_fvt_env: "false" + # aiservice tenant_id: "aiservice-t01" aiservice_namespace: "aiservice-inst-1-aiservice" diff --git a/instance-applications/120-ibm-db2u-database/templates/03-db2uinstance.yaml b/instance-applications/120-ibm-db2u-database/templates/03-db2uinstance.yaml index 13b0040fa..bfb058185 100644 --- a/instance-applications/120-ibm-db2u-database/templates/03-db2uinstance.yaml +++ b/instance-applications/120-ibm-db2u-database/templates/03-db2uinstance.yaml @@ -59,6 +59,15 @@ spec: secretName: "db2u-certificate-{{ .Values.db2_instance_name }}" certLabel: "CN=db2u" allowSslOnly: true +{{- if .Values.db2_ldap_enabled }} + authentication: + ldap: + enabled: {{ .Values.db2_ldap_enabled }} +{{- else }} + authentication: + ldap: + enabled: false +{{- end }} instance: {{- if .Values.db2_instance_registry }} diff --git a/instance-applications/120-ibm-db2u-database/templates/07-postsync-setup-db2_Job.yaml b/instance-applications/120-ibm-db2u-database/templates/07-postsync-setup-db2_Job.yaml index 1890bd2e7..be0f7dfed 100644 --- a/instance-applications/120-ibm-db2u-database/templates/07-postsync-setup-db2_Job.yaml +++ b/instance-applications/120-ibm-db2u-database/templates/07-postsync-setup-db2_Job.yaml @@ -87,7 +87,7 @@ data: db2_backup_bucket_secret_key: {{ .Values.db2_backup_bucket_secret_key | b64enc }} {{- if .Values.db2_backup_icd_auth_key }} icd_auth_key: {{ .Values.db2_backup_icd_auth_key | b64enc }} -{{- end }} +{{- end }} {{- end }} type: Opaque @@ -139,15 +139,22 @@ rules: - list - apiGroups: - "" - resources: + resources: - services - verbs: + verbs: - "get" +- apiGroups: + - "" + resources: + - secrets + verbs: + - get + - list - apiGroups: - db2u.databases.ibm.com - resources: + resources: - db2uinstances - verbs: + verbs: - "get" --- @@ -306,10 +313,15 @@ spec: RETRIES=${1:-5} RETRY_DELAY_SECONDS=${2:-30} - mas-devops-db2-validate-config --mas-instance-id ${MAS_INSTANCE_ID} --mas-app-id ${MAS_APP_ID} --log-level DEBUG || rc=$? - if [[ "$rc" == "0" ]]; then - echo "... db2 config already matches expected config, returning without calling apply-db2cfg-settings" - return 0 + # Skip validation for AI Service (MAS_APP_ID is empty or "aiservice") + if [[ -n "${MAS_APP_ID}" && "${MAS_APP_ID}" != "aiservice" ]]; then + mas-devops-db2-validate-config --mas-instance-id ${MAS_INSTANCE_ID} --mas-app-id ${MAS_APP_ID} --log-level DEBUG || rc=$? + if [[ "$rc" == "0" ]]; then + echo "... db2 config already matches expected config, returning without calling apply-db2cfg-settings" + return 0 + fi + else + echo "... skipping db2 config validation for AI Service (MAS_APP_ID is empty or aiservice)" fi for (( c=1; c<="${RETRIES}"; c++ )); do @@ -318,10 +330,16 @@ spec: oc exec -n ${DB2_NAMESPACE} c-${DB2_INSTANCE_NAME}-db2u-0 -- su -lc '/db2u/scripts/apply-db2cfg-settings.sh --setting all | tee /tmp/apply-db2cfg-settings.log' db2inst1 # no useful info in return code of this script - rc=0 - mas-devops-db2-validate-config --mas-instance-id ${MAS_INSTANCE_ID} --mas-app-id ${MAS_APP_ID} --log-level DEBUG || rc=$? - if [[ "$rc" == "0" ]]; then - echo "...... success" + # Skip validation for AI Service + if [[ -n "${MAS_APP_ID}" && "${MAS_APP_ID}" != "aiservice" ]]; then + rc=0 + mas-devops-db2-validate-config --mas-instance-id ${MAS_INSTANCE_ID} --mas-app-id ${MAS_APP_ID} --log-level DEBUG || rc=$? + if [[ "$rc" == "0" ]]; then + echo "...... success" + return 0 + fi + else + echo "...... skipping validation for AI Service, assuming success" return 0 fi @@ -634,8 +652,15 @@ spec: fi # check if config LOGARCHMETH1 is defined - config_op=`oc get db2uinstances db2wh-${MAS_INSTANCE_ID}-${MAS_APP_ID} -n ${DB2_NAMESPACE} -o json | jq -r '.spec.environment.databases' | jq ".[] | select(.name==\"${DB2_DBNAME}\")" | jq '.dbConfig.LOGARCHMETH1' | sed 's/DISK://'` - if [[ "${config_op}" != "null" ]]; then + # For AI Service (empty MAS_APP_ID), use DB2_INSTANCE_NAME directly + if [[ -z "${MAS_APP_ID}" || "${MAS_APP_ID}" == "aiservice" ]]; then + DB2U_INSTANCE_NAME="${DB2_INSTANCE_NAME}" + else + DB2U_INSTANCE_NAME="db2wh-${MAS_INSTANCE_ID}-${MAS_APP_ID}" + fi + + config_op=`oc get db2uinstances ${DB2U_INSTANCE_NAME} -n ${DB2_NAMESPACE} -o json | jq -r '.spec.environment.databases' | jq ".[] | select(.name==\"${DB2_DBNAME}\")" | jq '.dbConfig.LOGARCHMETH1' | sed 's/DISK://'` + if [[ "${config_op}" != "null" && -n "${config_op}" ]]; then echo "" echo "Creating directory ${config_op} for LOGARCHMETH1 on ${DB2_NAMESPACE}/c-${DB2_INSTANCE_NAME}-db2u-0" echo "--------------------------------------------------------------------------------" @@ -788,6 +813,38 @@ spec: TAGS="[{\"Key\": \"source\", \"Value\": \"postsync-setup-db2\"}, {\"Key\": \"account\", \"Value\": \"${ACCOUNT_ID}\"}, {\"Key\": \"cluster\", \"Value\": \"${CLUSTER_ID}\"}]" sm_update_secret ${DB2_CONFIG_SECRET} "{ \"jdbc_connection_url\": \"${JDBC_CONNECTION_URL}\", \"jdbc_instance_name\": \"${DB2_INSTANCE_NAME}\", \"ca_b64\": \"${DB2_CA_PEM}\", \"db2_dbname\": \"${DB2_DBNAME}\", \"db2_namespace\": \"${DB2_NAMESPACE}\" }" "${TAGS}" || exit $? + # Update JDBC secret for AI Service (if MAS_APP_ID is empty or "aiservice") + if [[ -z "${MAS_APP_ID}" || "${MAS_APP_ID}" == "aiservice" ]]; then + echo "" + echo "================================================================================" + echo "Updating JDBC secret for AI Service" + echo "================================================================================" + + # Fetch DB2 instance password + echo "" + echo "Fetching DB2 instance password from c-${DB2_INSTANCE_NAME}-instancepassword secret" + echo "--------------------------------------------------------------------------------" + export DB2_PASSWORD=$(oc get secret c-${DB2_INSTANCE_NAME}-instancepassword -n ${DB2_NAMESPACE} -o jsonpath='{.data.password}' | base64 -d) + if [[ -z "${DB2_PASSWORD}" ]]; then + echo "Failed to fetch DB2 instance password" + exit 1 + fi + + # Construct JDBC secret name + JDBC_SECRET_NAME="${ACCOUNT_ID}/${CLUSTER_ID}/jdbc" + + echo "" + echo "Updating JDBC secret: ${JDBC_SECRET_NAME}" + echo "--------------------------------------------------------------------------------" + + # Update JDBC secret with real DB2 connection details + JDBC_TAGS="[{\"Key\": \"jdbccfg_username\", \"Value\": \"jdbccfg_username\"}, {\"Key\": \"jdbccfg_password\", \"Value\": \"jdbccfg_password\"}, {\"Key\": \"jdbccfg_url\", \"Value\": \"jdbccfg_url\"}, {\"Key\": \"jdbccfg_sslenabled\", \"Value\": \"jdbccfg_sslenabled\"}, {\"Key\": \"jdbccfg_ca_b64enc\", \"Value\": \"jdbccfg_ca_b64enc\"}]" + + sm_update_secret "${JDBC_SECRET_NAME}" "{\"jdbccfg_username\":\"db2inst1\",\"jdbccfg_password\":\"${DB2_PASSWORD}\",\"jdbccfg_url\":\"${JDBC_CONNECTION_URL}\",\"jdbccfg_sslenabled\":\"true\",\"jdbccfg_ca_b64enc\":\"${DB2_CA_PEM}\"}" "${JDBC_TAGS}" || exit $? + + echo "✓ JDBC secret updated successfully for AI Service" + fi + if [[ -n $BUCKET_NAME ]]; then echo "" echo "================================================================================" diff --git a/root-applications/ibm-aiservice-instance-root/README.md b/root-applications/ibm-aiservice-instance-root/README.md index 102dc473a..3636661a9 100644 --- a/root-applications/ibm-aiservice-instance-root/README.md +++ b/root-applications/ibm-aiservice-instance-root/README.md @@ -180,6 +180,8 @@ The following table lists all ArgoCD applications defined in the templates folde | Template File | Application Name | Cluster Admin Role | Application Admin Role | Both Roles | |--------------|------------------|-------------------|----------------------|------------| +| [`010-ibm-db2u-app.yaml`](templates/010-ibm-db2u-app.yaml) | db2u | - | - | ✓ | +| [`020-ibm-db2u-database-app.yaml`](templates/020-ibm-db2u-database-app.yaml) | db2u-database | - | - | ✓ | | [`030-ibm-odh-app.yaml`](templates/030-ibm-odh-app.yaml) | odh | - | - | ✓ | | [`031-ibm-rhoai-app.yaml`](templates/031-ibm-rhoai-app.yaml) | rhoai | - | - | ✓ | | [`040-ibm-aiservice-app.yaml`](templates/040-ibm-aiservice-app.yaml) | aiservice | - | - | ✓ | @@ -189,7 +191,7 @@ The following table lists all ArgoCD applications defined in the templates folde - **Cluster Admin Role**: Applications that require `cluster_admin_role` to be set (0 applications) - **Application Admin Role**: Applications that require `application_admin_role` to be set (0 applications) -- **Both Roles**: Applications rendered regardless of role settings (3 applications) +- **Both Roles**: Applications rendered regardless of role settings (6 applications) **Note**: Some applications have additional conditions beyond role requirements (e.g., specific configuration values must be defined). Refer to individual template files for complete rendering logic. diff --git a/root-applications/ibm-aiservice-instance-root/templates/010-ibm-db2u-app.yaml b/root-applications/ibm-aiservice-instance-root/templates/010-ibm-db2u-app.yaml new file mode 100644 index 000000000..8478a02b5 --- /dev/null +++ b/root-applications/ibm-aiservice-instance-root/templates/010-ibm-db2u-app.yaml @@ -0,0 +1,87 @@ +{{- if not (empty .Values.ibm_db2u) }} +--- +# IBM DB2U Operator Application for AI Service +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: db2u-operator.{{ .Values.cluster.id }}.{{ .Values.instance.id }} + namespace: {{ .Values.argo.namespace }} + labels: + environment: '{{ .Values.account.id }}' + region: '{{ .Values.region.id }}' + cluster: '{{ .Values.cluster.id }}' + {{- if .Values.argo.instance }} + argocd.argoproj.io/instance: '{{ .Values.argo.instance }}' + {{- end }} + instance: '{{ .Values.instance.id }}' + annotations: + argocd.argoproj.io/sync-wave: "010" + healthCheckTimeout: "1800" + {{- if and .Values.notifications .Values.notifications.slack_channel_id }} + notifications.argoproj.io/subscribe.on-sync-failed.workspace1: {{ .Values.notifications.slack_channel_id }} + notifications.argoproj.io/subscribe.on-sync-succeeded.workspace1: {{ .Values.notifications.slack_channel_id }} + {{- end }} + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: "{{ .Values.argo.projects.apps }}" + destination: + server: {{ .Values.cluster.url }} + namespace: "{{ .Values.ibm_db2u.db2_namespace }}" + source: + repoURL: "{{ .Values.source.repo_url }}" + path: instance-applications/110-ibm-db2u + targetRevision: "{{ .Values.source.revision }}" + plugin: + name: {{ .Values.avp.name }} + env: + - name: {{ .Values.avp.values_varname }} + value: | + argo_namespace: "{{ .Values.argo.namespace }}" + db2_namespace: "{{ .Values.ibm_db2u.db2_namespace }}" + cli_image_repo: {{ .Values.cli_image_repo }} + cluster_admin_role: {{ .Values.cluster_admin_role }} + application_admin_role: {{ .Values.application_admin_role }} + ibm_entitlement_key: "{{ .Values.ibm_db2u.ibm_entitlement_key }}" + db2_channel: "{{ .Values.ibm_db2u.db2_channel }}" + db2_install_plan: "{{ .Values.ibm_db2u.db2_install_plan }}" + {{- if .Values.custom_labels }} + custom_labels: {{ .Values.custom_labels | toYaml | nindent 14 }} + {{- end }} + junitreporter: + reporter_name: "ibm-db2u-operator-{{ .Values.instance.id }}" + cluster_id: "{{ .Values.cluster.id }}" + instance_id: "{{ .Values.instance.id }}" + devops_mongo_uri: "{{ .Values.devops.mongo_uri }}" + devops_build_number: "{{ .Values.devops.build_number }}" + gitops_version: "{{ .Values.source.revision }}" + cli_image_repo: {{ .Values.cli_image_repo }} + - name: ARGOCD_APP_NAME + value: db2u-operator + {{- if not (empty .Values.avp.secret) }} + - name: AVP_SECRET + value: {{ .Values.avp.secret }} + {{- end }} + syncPolicy: + automated: + {{- if .Values.auto_delete }} + prune: true + {{- end }} + selfHeal: true + retry: + limit: 20 + syncOptions: + - CreateNamespace=true +{{- if or .Values.custom_labels .Values.argocluster_instance }} + managedNamespaceMetadata: + labels: + {{- if .Values.argocluster_instance }} + argocd.argoproj.io/managed-by: {{ .Values.argocluster_instance }} + {{- end }} + {{- if .Values.custom_labels }} +{{ .Values.custom_labels | toYaml | indent 8 }} + {{- end }} +{{- end }} +{{- end }} + +# Made with Bob diff --git a/root-applications/ibm-aiservice-instance-root/templates/020-ibm-db2u-database-app.yaml b/root-applications/ibm-aiservice-instance-root/templates/020-ibm-db2u-database-app.yaml new file mode 100644 index 000000000..c9c6ed373 --- /dev/null +++ b/root-applications/ibm-aiservice-instance-root/templates/020-ibm-db2u-database-app.yaml @@ -0,0 +1,93 @@ +{{- if not (empty .Values.ibm_db2u_databases) }} +--- +# IBM DB2U Application for AI Service +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: db2u.{{ .Values.cluster.id }}.{{ .Values.instance.id }} + namespace: {{ .Values.argo.namespace }} + labels: + environment: '{{ .Values.account.id }}' + region: '{{ .Values.region.id }}' + cluster: '{{ .Values.cluster.id }}' + {{- if .Values.argo.instance }} + argocd.argoproj.io/instance: '{{ .Values.argo.instance }}' + {{- end }} + instance: '{{ .Values.instance.id }}' + annotations: + argocd.argoproj.io/sync-wave: "020" + healthCheckTimeout: "1800" + {{- if and .Values.notifications .Values.notifications.slack_channel_id }} + notifications.argoproj.io/subscribe.on-sync-failed.workspace1: {{ .Values.notifications.slack_channel_id }} + notifications.argoproj.io/subscribe.on-sync-succeeded.workspace1: {{ .Values.notifications.slack_channel_id }} + {{- end }} + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: "{{ .Values.argo.projects.apps }}" + destination: + server: {{ .Values.cluster.url }} + namespace: "{{ .Values.ibm_db2u.db2_namespace }}" + source: + repoURL: "{{ .Values.source.repo_url }}" + path: instance-applications/120-ibm-db2u-database + targetRevision: "{{ .Values.source.revision }}" + plugin: + name: {{ .Values.avp.name }} + env: + - name: {{ .Values.avp.values_varname }} + value: | + account_id: "{{ .Values.account.id }}" + region_id: "{{ .Values.region.id }}" + cluster_id: "{{ .Values.cluster.id }}" + instance_id: "{{ .Values.instance.id }}" + sm_aws_access_key_id: "{{ .Values.sm.aws_access_key_id }}" + sm_aws_secret_access_key: "{{ .Values.sm.aws_secret_access_key }}" + cluster_admin_role: {{ .Values.cluster_admin_role }} + application_admin_role: {{ .Values.application_admin_role }} + should_execute: true + + {{- if .Values.custom_labels }} + custom_labels: {{ .Values.custom_labels | toYaml | nindent 12 }} + {{- end }} + + {{- if .Values.ibm_db2u_databases }} + {{ index .Values.ibm_db2u_databases 0 | toYaml | nindent 12 }} + {{- end }} + + - name: ARGOCD_APP_NAME + value: db2uapp + {{- if not (empty .Values.avp.secret) }} + - name: AVP_SECRET + value: {{ .Values.avp.secret }} + {{- end }} + syncPolicy: + automated: + {{- if .Values.auto_delete }} + prune: true + {{- end }} + selfHeal: true + retry: + limit: 20 + syncOptions: + - CreateNamespace=true + - RespectIgnoreDifferences=true + - ApplyOutOfSyncOnly=true + ignoreDifferences: + - group: 'db2u.databases.ibm.com' + kind: Db2uInstance + jsonPointers: + - /spec/environment/ssl/secretName +{{- if or .Values.custom_labels .Values.argocluster_instance }} + managedNamespaceMetadata: + labels: + {{- if .Values.argocluster_instance }} + argocd.argoproj.io/managed-by: {{ .Values.argocluster_instance }} + {{- end }} + {{- if .Values.custom_labels }} +{{ .Values.custom_labels | toYaml | indent 8 }} + {{- end }} +{{- end }} +{{- end }} + +# Made with Bob diff --git a/root-applications/ibm-aiservice-instance-root/templates/040-ibm-aiservice-app.yaml b/root-applications/ibm-aiservice-instance-root/templates/040-ibm-aiservice-app.yaml index 9cdf05219..8119da868 100644 --- a/root-applications/ibm-aiservice-instance-root/templates/040-ibm-aiservice-app.yaml +++ b/root-applications/ibm-aiservice-instance-root/templates/040-ibm-aiservice-app.yaml @@ -51,7 +51,7 @@ spec: aiservice_s3_port: "{{ .Values.ibm_aiservice.aiservice_s3_port }}" dro_ca_b64enc: "{{ .Values.ibm_aiservice.dro_ca_b64enc }}" - jdbccfg_ca_b64enc: "{{ .Values.ibm_aiservice.jdbccfg_ca_b64enc }}" + jdbccfg_ca_b64enc: "{{ .Values.ibm_aiservice.jdbccfg_ca_b64enc | default "" }}" aiservice_dro_token_secret: "{{ .Values.ibm_aiservice.aiservice_dro_token_secret }}" aiservice_dro_cacert_secret: "{{ .Values.ibm_aiservice.aiservice_dro_cacert_secret }}" @@ -62,11 +62,11 @@ spec: aiservice_jdbc_secret: "{{ .Values.ibm_aiservice.aiservice_jdbc_secret }}" use_aws_db2: "{{ .Values.ibm_aiservice.use_aws_db2 }}" - jdbccfg_username: "{{ .Values.ibm_aiservice.jdbccfg_username }}" - jdbccfg_password: "{{ .Values.ibm_aiservice.jdbccfg_password }}" - jdbccfg_url: "{{ .Values.ibm_aiservice.jdbccfg_url }}" - jdbccfg_sslenabled: "{{ .Values.ibm_aiservice.jdbccfg_sslenabled }}" - jdbccfg_ca: "{{ .Values.ibm_aiservice.jdbccfg_ca }}" + jdbccfg_username: "{{ .Values.ibm_aiservice.jdbccfg_username | default "" }}" + jdbccfg_password: "{{ .Values.ibm_aiservice.jdbccfg_password | default "" }}" + jdbccfg_url: "{{ .Values.ibm_aiservice.jdbccfg_url | default "" }}" + jdbccfg_sslenabled: "{{ .Values.ibm_aiservice.jdbccfg_sslenabled | default "" }}" + jdbccfg_ca: "{{ .Values.ibm_aiservice.jdbccfg_ca | default "" }}" entitlement_username: "{{ .Values.ibm_aiservice.entitlement_username }}" entitlement_key: "{{ .Values.ibm_aiservice.entitlement_key }}" @@ -120,6 +120,7 @@ spec: {{- if .Values.cluster_admin_role }} - CreateNamespace=true {{- end }} + - ApplyOutOfSyncOnly=true {{- if .Values.custom_labels }} managedNamespaceMetadata: labels: diff --git a/root-applications/ibm-aiservice-instance-root/templates/070-aiservice-tenant-appset.yaml b/root-applications/ibm-aiservice-instance-root/templates/070-aiservice-tenant-appset.yaml index 79183b39e..217a4dcfa 100644 --- a/root-applications/ibm-aiservice-instance-root/templates/070-aiservice-tenant-appset.yaml +++ b/root-applications/ibm-aiservice-instance-root/templates/070-aiservice-tenant-appset.yaml @@ -121,6 +121,7 @@ spec: - CreateNamespace=false - RespectIgnoreDifferences=true - ServerSideApply=true + - ApplyOutOfSyncOnly=true retry: limit: -1 ignoreDifferences: diff --git a/root-applications/ibm-aiservice-tenant-root/templates/100-ibm-aiservice-tenant-app.yaml b/root-applications/ibm-aiservice-tenant-root/templates/100-ibm-aiservice-tenant-app.yaml index 25efb651f..38b0fe53f 100644 --- a/root-applications/ibm-aiservice-tenant-root/templates/100-ibm-aiservice-tenant-app.yaml +++ b/root-applications/ibm-aiservice-tenant-root/templates/100-ibm-aiservice-tenant-app.yaml @@ -54,6 +54,7 @@ spec: account_id: "{{ .Values.account.id }}" region_id: "{{ .Values.region.id }}" cluster_id: "{{ .Values.cluster.id }}" + cluster_url: "{{ .Values.cluster.url }}" cluster_admin_role: {{ if hasKey .Values "cluster_admin_role" }}{{ .Values.cluster_admin_role }}{{ else }}true{{ end }} application_admin_role: {{ if hasKey .Values "application_admin_role" }}{{ .Values.application_admin_role }}{{ else }}true{{ end }} # SAAS @@ -80,6 +81,11 @@ spec: slscfg_url: "{{ .Values.ibm_aiservice_tenant.slscfg_url }}" slscfg_ca_b64enc: "{{ .Values.ibm_aiservice_tenant.slscfg_ca_b64enc }}" aiservice_sls_subscription_id: "{{ .Values.ibm_aiservice_tenant.aiservice_sls_subscription_id }}" + {{- if .Values.ibm_aiservice_tenant.ibm_customer_number }} + ibm_customer_number: "{{ .Values.ibm_aiservice_tenant.ibm_customer_number }}" + {{- end }} + sm_aws_access_key_id: "{{ .Values.sm.aws_access_key_id }}" + sm_aws_secret_access_key: "{{ .Values.sm.aws_secret_access_key }}" #s3 postsync manage aiservice_s3_accesskey: "{{ .Values.ibm_aiservice_tenant.aiservice_s3_accesskey }}" diff --git a/root-applications/ibm-mas-cluster-root/templates/099-aiservice-instance-appset.yaml b/root-applications/ibm-mas-cluster-root/templates/099-aiservice-instance-appset.yaml index 1a68d6a0a..d07203e01 100644 --- a/root-applications/ibm-mas-cluster-root/templates/099-aiservice-instance-appset.yaml +++ b/root-applications/ibm-mas-cluster-root/templates/099-aiservice-instance-appset.yaml @@ -32,6 +32,11 @@ spec: revision: "{{ .Values.generator.revision }}" files: - path: "{{ .Values.account.id }}/{{ .Values.cluster.id }}/*/ibm-aiservice-instance-base.yaml" + - git: + repoURL: "{{ .Values.generator.repo_url }}" + revision: "{{ .Values.generator.revision }}" + files: + - path: "{{ .Values.account.id }}/{{ .Values.cluster.id }}/*/ibm-db2u.yaml" - git: repoURL: "{{ .Values.generator.repo_url }}" revision: "{{ .Values.generator.revision }}" @@ -65,13 +70,23 @@ spec: {{- end }} annotations: healthCheckTimeout: "1800" - argocd.argoproj.io/sync-wave: "099" + argocd.argoproj.io/sync-wave: "130" {{- if and .Values.notifications .Values.notifications.slack_channel_id }} notifications.argoproj.io/subscribe.on-sync-failed.workspace1: {{ .Values.notifications.slack_channel_id }} notifications.argoproj.io/subscribe.on-sync-succeeded.workspace1: {{ .Values.notifications.slack_channel_id }} {{- end }} spec: project: "{{ .Values.argo.projects.rootapps }}" + syncPolicy: + automated: + prune: {{ if .Values.auto_delete }}true{{ else }}false{{ end }} + selfHeal: true + retry: + limit: 10 + backoff: + duration: 30s + factor: 2 + maxDuration: 5m source: repoURL: "{{ .Values.source.repo_url }}" targetRevision: "{{ .Values.source.revision }}"