set fallback := true export SUPERSET_NAMESPACE := env("SUPERSET_NAMESPACE", "superset") export SUPERSET_CHART_VERSION := env("SUPERSET_CHART_VERSION", "0.15.0") export SUPERSET_HOST := env("SUPERSET_HOST", "") export EXTERNAL_SECRETS_NAMESPACE := env("EXTERNAL_SECRETS_NAMESPACE", "external-secrets") export K8S_VAULT_NAMESPACE := env("K8S_VAULT_NAMESPACE", "vault") export KEYCLOAK_REALM := env("KEYCLOAK_REALM", "buunstack") export KEYCLOAK_HOST := env("KEYCLOAK_HOST", "") export SUPERSET_CPU_REQUEST := env("SUPERSET_CPU_REQUEST", "50m") export SUPERSET_CPU_LIMIT := env("SUPERSET_CPU_LIMIT", "1") export SUPERSET_MEMORY_REQUEST := env("SUPERSET_MEMORY_REQUEST", "500Mi") export SUPERSET_MEMORY_LIMIT := env("SUPERSET_MEMORY_LIMIT", "3Gi") export SUPERSET_WORKER_CPU_REQUEST := env("SUPERSET_WORKER_CPU_REQUEST", "300m") export SUPERSET_WORKER_CPU_LIMIT := env("SUPERSET_WORKER_CPU_LIMIT", "4") export SUPERSET_WORKER_MEMORY_REQUEST := env("SUPERSET_WORKER_MEMORY_REQUEST", "4Gi") export SUPERSET_WORKER_MEMORY_LIMIT := env("SUPERSET_WORKER_MEMORY_LIMIT", "8Gi") export REDIS_CPU_REQUEST := env("REDIS_CPU_REQUEST", "50m") export REDIS_CPU_LIMIT := env("REDIS_CPU_LIMIT", "200m") export REDIS_MEMORY_REQUEST := env("REDIS_MEMORY_REQUEST", "128Mi") export REDIS_MEMORY_LIMIT := env("REDIS_MEMORY_LIMIT", "256Mi") [private] default: @just --list --unsorted --list-submodules # Add Helm repository add-helm-repo: helm repo add superset https://apache.github.io/superset helm repo update # Remove Helm repository remove-helm-repo: helm repo remove superset # Create Superset namespace create-namespace: @kubectl get namespace ${SUPERSET_NAMESPACE} &>/dev/null || \ kubectl create namespace ${SUPERSET_NAMESPACE} # Delete Superset namespace delete-namespace: @kubectl delete namespace ${SUPERSET_NAMESPACE} --ignore-not-found # Create Keycloak client and OAuth secret for Superset create-keycloak-client: #!/bin/bash set -euo pipefail while [ -z "${SUPERSET_HOST}" ]; do SUPERSET_HOST=$( gum input --prompt="Superset host (FQDN): " --width=100 \ --placeholder="e.g., superset.example.com" ) done echo "Creating Keycloak client for Superset..." just keycloak::delete-client ${KEYCLOAK_REALM} superset || true CLIENT_SECRET=$(just utils::random-password) just keycloak::create-group superset-admin '' 'Superset administrators' || echo "Group may already exist" just keycloak::create-client \ realm=${KEYCLOAK_REALM} \ client_id=superset \ redirect_url="https://${SUPERSET_HOST}/oauth-authorized/keycloak" \ client_secret="${CLIENT_SECRET}" just keycloak::add-groups-mapper superset kubectl delete secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} --ignore-not-found kubectl create secret generic superset-oauth-temp -n ${SUPERSET_NAMESPACE} \ --from-literal=client_secret="${CLIENT_SECRET}" echo "Keycloak client created successfully" echo "Client ID: superset" echo "Redirect URI: https://${SUPERSET_HOST}/oauth-authorized/keycloak" echo "" echo "Admin Group: superset-admin" echo "To grant admin access, add users to 'superset-admin' group:" echo " just keycloak::add-user-to-group superset-admin" # Delete Keycloak client delete-keycloak-client: #!/bin/bash set -euo pipefail echo "Deleting Keycloak client for Superset..." just keycloak::delete-client ${KEYCLOAK_REALM} superset || true echo "Deleting superset-admin group..." just keycloak::delete-group superset-admin || true kubectl delete secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} --ignore-not-found # Create Superset secrets create-secrets postgres_password='': #!/bin/bash set -euo pipefail pg_host="postgres-cluster-rw.postgres" pg_port="5432" pg_user="superset" pg_password="{{ postgres_password }}" pg_database="superset" database_url="postgresql://${pg_user}:${pg_password}@${pg_host}:${pg_port}/${pg_database}" if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null && \ just vault::get superset/oauth client_secret &>/dev/null; then oauth_client_secret=$(just vault::get superset/oauth client_secret) elif kubectl get secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} &>/dev/null; then oauth_client_secret=$(kubectl get secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} \ -o jsonpath='{.data.client_secret}' | base64 -d) else echo "Error: Cannot retrieve OAuth client secret. Please run 'just superset::create-keycloak-client' first." >&2 exit 1 fi if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then echo "External Secrets Operator detected. Storing secrets in Vault..." # Try to retrieve existing SECRET_KEY, generate new one if not found if secret_key=$(just vault::get superset/config SECRET_KEY 2>/dev/null); then echo "Using existing SECRET_KEY from Vault." else echo "Generating new SECRET_KEY..." secret_key=$(just utils::random-password) fi just vault::put superset/config \ SECRET_KEY="${secret_key}" \ SQLALCHEMY_DATABASE_URI="${database_url}" \ OAUTH_CLIENT_SECRET="${oauth_client_secret}" \ DB_PASSWORD="${pg_password}" kubectl delete secret superset-secret -n ${SUPERSET_NAMESPACE} --ignore-not-found kubectl delete externalsecret superset-secret -n ${SUPERSET_NAMESPACE} --ignore-not-found gomplate -f superset-config-external-secret.gomplate.yaml \ -o superset-config-external-secret.yaml kubectl apply -f superset-config-external-secret.yaml echo "Waiting for ExternalSecret to sync..." kubectl wait --for=condition=Ready externalsecret/superset-secret \ -n ${SUPERSET_NAMESPACE} --timeout=60s else echo "External Secrets Operator not found. Creating secret directly..." kubectl delete secret superset-secret -n ${SUPERSET_NAMESPACE} --ignore-not-found kubectl create secret generic superset-secret -n ${SUPERSET_NAMESPACE} \ --from-literal=SECRET_KEY="${secret_key}" \ --from-literal=SQLALCHEMY_DATABASE_URI="${database_url}" \ --from-literal=OAUTH_CLIENT_SECRET="${oauth_client_secret}" fi # Delete Superset secrets delete-secrets: @kubectl delete secret superset-secret -n ${SUPERSET_NAMESPACE} --ignore-not-found @kubectl delete externalsecret superset-secret -n ${SUPERSET_NAMESPACE} --ignore-not-found # Install Superset install: #!/bin/bash set -euo pipefail while [ -z "${SUPERSET_HOST}" ]; do SUPERSET_HOST=$( gum input --prompt="Superset host (FQDN): " --width=100 \ --placeholder="e.g., superset.example.com" ) done while [ -z "${KEYCLOAK_HOST}" ]; do KEYCLOAK_HOST=$( gum input --prompt="Keycloak host (FQDN): " --width=100 \ --placeholder="e.g., auth.example.com" ) done just create-namespace kubectl label namespace ${SUPERSET_NAMESPACE} \ pod-security.kubernetes.io/enforce=baseline --overwrite # Create Superset database and user if just postgres::user-exists superset &>/dev/null; then echo "PostgreSQL user 'superset' already exists." if helm status external-secrets -n ${EXTERNAL_SECRETS_NAMESPACE} &>/dev/null; then POSTGRES_PASSWORD=$(just vault::get superset/config DB_PASSWORD) else echo "Without External Secrets Operator, cannot retrieve DB password." >&2 echo "Please dump the database and run restore after installation:" >&2 echo "just superset::restore " >&2 exit 1 fi else echo "Creating new PostgreSQL user and database..." POSTGRES_PASSWORD=$(just utils::random-password) just postgres::create-user-and-db superset superset "${POSTGRES_PASSWORD}" fi just create-keycloak-client just create-secrets "${POSTGRES_PASSWORD}" if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null && \ just vault::get superset/oauth client_secret &>/dev/null; then export OAUTH_CLIENT_SECRET=$(just vault::get superset/oauth client_secret) elif kubectl get secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} &>/dev/null; then export OAUTH_CLIENT_SECRET=$(kubectl get secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} \ -o jsonpath='{.data.client_secret}' | base64 -d) else echo "Error: Cannot retrieve OAuth client secret. Please run 'just superset::create-keycloak-client' first." >&2 exit 1 fi export SUPERSET_DB_PASSWORD="${POSTGRES_PASSWORD}" just add-helm-repo gomplate -f superset-values.gomplate.yaml -o superset-values.yaml helm upgrade --cleanup-on-fail --install superset superset/superset \ --version ${SUPERSET_CHART_VERSION} -n ${SUPERSET_NAMESPACE} --wait \ -f superset-values.yaml echo "" echo "Superset installed successfully!" echo "Access URL: https://${SUPERSET_HOST}" echo "" echo "OAuth Configuration:" echo " Provider: Keycloak" echo " Realm: ${KEYCLOAK_REALM}" echo " Authorization URL: https://${KEYCLOAK_HOST}/realms/${KEYCLOAK_REALM}/protocol/openid-connect/auth" echo "" echo "Admin Access:" echo " To grant admin access, add users to 'superset-admin' group:" echo " just keycloak::add-user-to-group superset-admin" echo "" # Upgrade Superset upgrade: #!/bin/bash set -euo pipefail while [ -z "${SUPERSET_HOST}" ]; do SUPERSET_HOST=$( gum input --prompt="Superset host (FQDN): " --width=100 \ --placeholder="e.g., superset.example.com" ) done while [ -z "${KEYCLOAK_HOST}" ]; do KEYCLOAK_HOST=$( gum input --prompt="Keycloak host (FQDN): " --width=100 \ --placeholder="e.g., auth.example.com" ) done if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null && \ just vault::get superset/oauth client_secret &>/dev/null; then export OAUTH_CLIENT_SECRET=$(just vault::get superset/oauth client_secret) elif kubectl get secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} &>/dev/null; then export OAUTH_CLIENT_SECRET=$(kubectl get secret superset-oauth-temp -n ${SUPERSET_NAMESPACE} \ -o jsonpath='{.data.client_secret}' | base64 -d) else echo "Error: Cannot retrieve OAuth client secret. Please run 'just superset::create-keycloak-client' first." >&2 exit 1 fi # Extract database password from SQLALCHEMY_DATABASE_URI in existing secret database_uri=$(kubectl get secret superset-secret -n ${SUPERSET_NAMESPACE} \ -o jsonpath='{.data.SQLALCHEMY_DATABASE_URI}' | base64 -d) export SUPERSET_DB_PASSWORD=$(echo "$database_uri" | sed -n 's|.*://[^:]*:\([^@]*\)@.*|\1|p') echo "Upgrading Superset..." gomplate -f superset-values.gomplate.yaml -o superset-values.yaml helm upgrade superset superset/superset \ --version ${SUPERSET_CHART_VERSION} -n ${SUPERSET_NAMESPACE} --wait \ -f superset-values.yaml echo "Superset upgraded successfully" # Uninstall Superset uninstall delete-db='true': #!/bin/bash set -euo pipefail helm uninstall superset -n ${SUPERSET_NAMESPACE} --ignore-not-found --wait just delete-secrets just delete-keycloak-client just delete-namespace if [ "{{ delete-db }}" = "true" ]; then just postgres::delete-user-and-db superset superset just vault::delete superset/config || true fi if helm status vault -n ${K8S_VAULT_NAMESPACE} &>/dev/null; then just vault::delete superset/oauth || true fi # Restore Superset datasets, charts, and dashboards from backup restore backup_file charts_only='false': #!/bin/bash set -euo pipefail BACKUP_FILE="{{ backup_file }}" CHARTS_ONLY="{{ charts_only }}" # Convert to absolute path if relative if [[ ! "${BACKUP_FILE}" = /* ]]; then BACKUP_FILE="../${BACKUP_FILE}" fi if [ ! -f "${BACKUP_FILE}" ]; then echo "Error: Backup file '${BACKUP_FILE}' not found" >&2 exit 1 fi POD_NAME=$(kubectl get pods -n postgres -l cnpg.io/cluster=postgres-cluster \ -o jsonpath='{.items[0].metadata.name}') if [ -z "${POD_NAME}" ]; then echo "Error: PostgreSQL pod not found" >&2 exit 1 fi echo "Uploading backup file to PostgreSQL pod..." kubectl cp "${BACKUP_FILE}" postgres/${POD_NAME}:/var/lib/postgresql/data/superset-restore.sql echo "Running restore script..." if [ "${CHARTS_ONLY}" = "true" ]; then bash restore-datasets-charts.sh --charts-only else bash restore-datasets-charts.sh fi echo "" echo "Restarting Superset pods to clear cache..." kubectl delete pod -n ${SUPERSET_NAMESPACE} -l app=superset --wait=false || true kubectl delete pod -n ${SUPERSET_NAMESPACE} -l app.kubernetes.io/component=worker --wait=false || true echo "" echo "Restore completed successfully!" echo "Please wait for Superset pods to restart."