Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.scriptConfigMapName | quote }}
name: {{ .Values.db2_rds_instance_name }}-init-script
annotations:
argocd.argoproj.io/sync-wave: "135"
{{- if .Values.custom_labels }}
Expand Down Expand Up @@ -60,7 +60,7 @@ data:

# Creating database connection string for db2rds user
conn_rds_db2_str = (
f"DATABASE={os.environ['DB_NAME']};"
f"DATABASE={database_name};"
f"HOSTNAME={os.environ['DB_HOST']};"
f"PORT={os.environ['DB_PORT']};"
f"PROTOCOL=TCPIP;"
Expand All @@ -70,13 +70,21 @@ data:
f"SSLVersion={ssl_version};"
f"SSLServerCertificate={ssl_cert_path};"
)

# Debug: Print connection strings with masked passwords
masked_admin_str = conn_rds_admin_str.replace(f"PWD={password}", "PWD=***MASKED***")
masked_db2_str = conn_rds_db2_str.replace(f"PWD={password}", "PWD=***MASKED***")
print(f"🔍 Admin connection string: {masked_admin_str}")
print(f"🔍 DB2 connection string: {masked_db2_str}")

# Connect to both databases
# Note: Database availability is checked by bash script before calling this Python script
try:
admin_conn = db2.connect(conn_rds_admin_str, "", "")
print(f"✅ Connected to Db2 as {username} user")
print(f"✅ Connected to RDS admin database as {username}")

db2_conn = db2.connect(conn_rds_db2_str, "", "")
print("✅ Connected to Db2 as db2rds user")
db2_conn = db2.connect(conn_rds_db2_str, "", "")
print(f"✅ Connected to database '{database_name}'")
except Exception as e:
print(f"❌ Connection failed: {e}")
sys.exit(1)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
{{- if .Values.application_admin_role }}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Values.db2_rds_instance_name }}-db-common-functions
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
{{- if .Values.custom_labels }}
labels:
{{ .Values.custom_labels | toYaml | indent 4 }}
{{- end }}
data:
db_common_functions.sh: |
#!/bin/bash
# Common functions for DB2 RDS database operations
# Used by both pre-sync (create database) and post-sync (setup) jobs

# Generate deterministic DB2 database name
generate_db_name() {
local instance_id="$1"
local mas_app_id="$2"

# Normalize INSTANCE_ID:
# - uppercase
# - remove non-alphanumeric
local inst_clean=$(echo "${instance_id:-}" \
| tr '[:lower:]' '[:upper:]' \
| tr -cd 'A-Z0-9')

# Normalize MAS_APP_ID:
# - remove vowels
# - uppercase
# - remove non-alphanumeric
local app_clean=$(echo "${mas_app_id:-}" \
| tr -d 'aeiouAEIOU' \
| tr '[:lower:]' '[:upper:]' \
| tr -cd 'A-Z0-9')

# Safety: if app name becomes empty after vowel removal, use default
if [ -z "${app_clean}" ]; then
app_clean="APP"
fi

# Build fixed-width parts
local inst_part=$(printf "%-5s" "${inst_clean}" | tr ' ' 'X' | cut -c1-5)
local app_part=$(printf "%-3s" "${app_clean}" | tr ' ' 'X' | cut -c1-3)

local db_name="${inst_part}${app_part}"

# Ensure DB name starts with a letter (DB2-safe)
case "${db_name}" in
[A-Z]*)
;;
*)
db_name="D${db_name:1}"
;;
esac

echo "${db_name}"
}

# Wait for database to be available (with polling)
# Uses the Python script from the pre-sync job
wait_for_database() {
local db_name="$1"
local timeout="${2:-600}" # Default 10 minutes
local interval="${3:-10}" # Default 10 seconds

echo "⏳ Waiting for database '${db_name}' to be available..."

# Set environment variables for Python script
export DB_NAME="${db_name}"
export DB_CREATE_TIMEOUT_SECONDS="${timeout}"
export DB_POLL_INTERVAL_SECONDS="${interval}"

# Run the database existence check using Python
python3 -c "
import ibm_db as db2
import os, sys, re, time, json

def log(msg):
print(json.dumps(msg))

def database_exists(db_name, db_host, db_port, username, password, ssl_cert_path, ssl_version):
try:
conn_str = (
f'DATABASE={db_name};'
f'HOSTNAME={db_host};'
f'PORT={db_port};'
f'PROTOCOL=TCPIP;'
f'UID={username};'
f'PWD={password};'
f'Security=SSL;'
f'SSLServerCertificate={ssl_cert_path};'
f'SSLVersion={ssl_version};'
)
conn = db2.connect(conn_str, '', '')
db2.close(conn)
return True
except Exception as e:
msg = str(e)
if any(code in msg for code in ['SQL1013N','SQLSTATE=42705','SQL30061N','SQLSTATE=08004','CLI0199E']):
return False
raise RuntimeError(f'Error checking database existence: {msg}')

# Read credentials
username = open('/etc/mas/dbs-rds-creds/username').read().strip()
password = open('/etc/mas/dbs-rds-creds/password').read().strip()

# Get environment variables
db_name = os.getenv('DB_NAME')
db_host = os.getenv('DB_HOST')
db_port = os.getenv('DB_PORT', '50000')
jdbc_url = os.getenv('JDBC_CONNECTION_URL', '')
ssl_cert_path = os.getenv('SSL_CERT_PATH', '/etc/mas/rds-ssl/global-bundle.pem')
timeout = int(os.getenv('DB_CREATE_TIMEOUT_SECONDS', '600'))
interval = int(os.getenv('DB_POLL_INTERVAL_SECONDS', '10'))

# Extract SSL version
ssl_version = 'TLSv1.2'
if jdbc_url:
m = re.search(r'sslVersion=([^;]+)', jdbc_url, re.IGNORECASE)
if m:
ssl_version = m.group(1)

# Wait for database
retries = max(1, timeout // interval)
for i in range(retries):
time.sleep(interval)
try:
if database_exists(db_name, db_host, db_port, username, password, ssl_cert_path, ssl_version):
log({'event':'db_available','db':db_name,'elapsed_seconds':(i+1)*interval})
sys.exit(0)
except RuntimeError as e:
msg = str(e)
if any(x in msg for x in ['SQLSTATE=57019','SQL1035N','SQL30081N']):
log({'event':'retry','attempt':i+1})
continue
log({'event':'error','msg':msg})
sys.exit(1)

log({'event':'timeout','db':db_name,'timeout_seconds':timeout})
sys.exit(1)
"
return $?
}
{{- end }}

Original file line number Diff line number Diff line change
Expand Up @@ -100,41 +100,13 @@ spec:
if [ -z "${DB_NAME:-}" ]; then
echo "ℹ️ No DB_NAME provided, generating deterministic DB name"

# Normalize INSTANCE_ID:
# - uppercase
# - remove non-alphanumeric
INST_CLEAN=$(echo "${INSTANCE_ID:-}" \
| tr '[:lower:]' '[:upper:]' \
| tr -cd 'A-Z0-9')
# Source common functions
source /etc/mas/db-common-functions/db_common_functions.sh

# Normalize MAS_APP_ID:
# - remove vowels
# - uppercase
# - remove non-alphanumeric
APP_CLEAN=$(echo "${MAS_APP_ID:-}" \
| tr -d 'aeiouAEIOU' \
| tr '[:lower:]' '[:upper:]' \
| tr -cd 'A-Z0-9')
# Generate database name using shared function
DB_NAME=$(generate_db_name "${INSTANCE_ID}" "${MAS_APP_ID}")

# Safety: if app name becomes empty after vowel removal, use default
if [ -z "${APP_CLEAN}" ]; then
APP_CLEAN="APP"
fi

# Build fixed-width parts
INST_PART=$(printf "%-5s" "${INST_CLEAN}" | tr ' ' 'X' | cut -c1-5)
APP_PART=$(printf "%-3s" "${APP_CLEAN}" | tr ' ' 'X' | cut -c1-3)

DB_NAME="${INST_PART}${APP_PART}"

# Ensure DB name starts with a letter (DB2-safe)
case "${DB_NAME}" in
[A-Z]*)
;;
*)
DB_NAME="D${DB_NAME:1}"
;;
esac
echo "✅ Generated DB_NAME: ${DB_NAME}"
else
echo "ℹ️ Using provided DB_NAME: ${DB_NAME}"
fi
Expand Down Expand Up @@ -255,6 +227,8 @@ spec:
mountPath: /etc/mas/creds/aws
- name: create-db-script
mountPath: /etc/mas/scripts/
- name: db-common-functions
mountPath: /etc/mas/db-common-functions/
volumes:
- name: dbs-rds-create-db-secret
secret:
Expand All @@ -266,5 +240,9 @@ spec:
configMap:
name: {{ .Values.db2_rds_instance_name }}-create-db-script
defaultMode: 0755
- name: db-common-functions
configMap:
name: {{ .Values.db2_rds_instance_name }}-db-common-functions
defaultMode: 0755

{{- end }}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
E.g. passing in a new environment variable.
Included in $_job_hash (see below).
*/}}
{{- $_job_version := "v1" }}
{{- $_job_version := "v2" }}

{{- /*
10 char hash appended to the job name taking into account $_job_config_values, $_job_version and $_cli_image_digest
Expand Down Expand Up @@ -62,9 +62,13 @@ apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: dbs-rds-secret-store
name: {{ .Values.db2_rds_instance_name }}-secret-store
annotations:
argocd.argoproj.io/sync-wave: "136"
{{- if .Values.custom_labels }}
labels:
{{ .Values.custom_labels | toYaml | indent 4 }}
{{- end }}
data:
username: {{ .Values.user | b64enc }}
password: {{ .Values.password | b64enc }}
Expand Down Expand Up @@ -105,6 +109,7 @@ spec:
args:
- |
set -e

echo "Downloading RDS SSL certificate..."
mkdir -p /etc/mas/rds-ssl
curl -sSL https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem -o /etc/mas/rds-ssl/global-bundle.pem
Expand All @@ -114,6 +119,29 @@ spec:
echo "Verifying ibm_db package..."
python3 -c "import ibm_db; print('✅ ibm_db version:', ibm_db.__version__)"

# Source common functions
source /etc/mas/db-common-functions/db_common_functions.sh

# If DB_NAME is empty, generate it using shared function
if [ -z "${DB_NAME:-}" ]; then
echo "⚠️ DB_NAME is empty, generating deterministic database name..."

# Generate database name using shared function
DB_NAME=$(generate_db_name "${INSTANCE_ID}" "${MAS_APP_ID}")

echo "✅ Generated DB_NAME: ${DB_NAME}"
export DB_NAME
else
echo "ℹ️ Using provided DB_NAME: ${DB_NAME}"
fi

# Wait for database to be available (created by pre-sync job)
if ! wait_for_database "${DB_NAME}" 600 10; then
echo "❌ Database '${DB_NAME}' is not available after waiting"
echo " The pre-sync job may have failed to create the database."
exit 1
fi

export SSL_CERT_PATH=/etc/mas/rds-ssl/global-bundle.pem && \
python /etc/mas/dbs-rds-init-script/dbs-rds-init.py
env:
Expand All @@ -128,9 +156,11 @@ spec:
- name: DB_PORT
value: {{ .Values.port | quote }}
- name: DB_NAME
value: {{ .Values.dbname | quote }}
value: {{ .Values.dbname | default "" | quote }}
- name: SSL_CERT_PATH
value: "/etc/mas/rds-ssl/global-bundle.pem"
- name: INSTANCE_ID
value: {{ .Values.instance_id | quote }}
{{- if .Values.db2_database_db_config }}
- name: DB2_DATABASE_CONFIG
value: {{ .Values.db2_database_db_config | toJson | quote }}
Expand All @@ -140,14 +170,20 @@ spec:
mountPath: /etc/mas/dbs-rds-creds/
- name: dbs-rds-init-script
mountPath: /etc/mas/dbs-rds-init-script/
- name: db-common-functions
mountPath: /etc/mas/db-common-functions/
restartPolicy: Never
volumes:
- name: dbs-rds-init-script
configMap:
name: {{ .Values.scriptConfigMapName | quote }}
name: {{ .Values.db2_rds_instance_name }}-init-script
- name: dbs-rds-secret-store
secret:
secretName: dbs-rds-secret-store
secretName: {{ .Values.db2_rds_instance_name }}-secret-store
defaultMode: 420
optional: false
- name: db-common-functions
configMap:
name: {{ .Values.db2_rds_instance_name }}-db-common-functions
defaultMode: 0755
{{- end }}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: "{{ .Values.scriptConfigMapName }}-backup"
name: "{{ .Values.db2_rds_instance_name }}-init-script-backup"
annotations:
argocd.argoproj.io/sync-wave: "137"
{{- if .Values.custom_labels }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ spec:
volumes:
- name: backup-script
configMap:
name: {{ .Values.scriptConfigMapName }}-backup
name: {{ .Values.db2_rds_instance_name }}-init-script-backup
defaultMode: 0755
- name: dbs-rds-secret-store
secret:
secretName: dbs-rds-secret-store
secretName: {{ .Values.db2_rds_instance_name }}-secret-store
defaultMode: 0420
optional: false
{{- end }}
Expand Down
Loading