Files
buun-stack/superset/restore-datasets-charts.sh
2025-11-23 00:45:15 +09:00

177 lines
6.1 KiB
Bash
Executable File

#!/bin/bash
# Restore only Superset datasets, charts, and dashboards from backup
#
# Usage:
# ./restore-datasets-charts.sh [--charts-only]
#
# Options:
# --charts-only Restore only charts and datasets (skip dashboards)
set -euo pipefail
NAMESPACE="superset"
POSTGRES_NAMESPACE="postgres"
BACKUP_FILE="${BACKUP_FILE:-/var/lib/postgresql/data/superset-restore.sql}"
DB_NAME="superset"
DB_USER="postgres" # Use superuser for restore
# Get PostgreSQL pod name
POD_NAME=$(kubectl get pods -n postgres -l cnpg.io/cluster=postgres-cluster \
-o jsonpath='{.items[0].metadata.name}')
# Get database password from secret
DB_PASSWORD=$(kubectl get secret -n postgres postgres-cluster-superuser -o jsonpath='{.data.password}' | base64 -d)
# Core tables for datasets and charts
CORE_TABLES=(
"tables" # Dataset metadata
"table_columns" # Dataset columns
"sql_metrics" # Dataset metrics
"slices" # Chart definitions
)
# Dashboard tables (restored by default)
DASHBOARD_TABLES=(
"dashboards" # Dashboard metadata
"dashboard_slices" # Chart-Dashboard relationships
"dashboard_user" # Dashboard-User relationships
"dashboard_roles" # Dashboard-Role relationships
"embedded_dashboards" # Embedded dashboard configurations
)
# Parse command line arguments
RESTORE_DASHBOARDS=true # Default: restore dashboards
for arg in "$@"; do
case $arg in
--charts-only)
RESTORE_DASHBOARDS=false
shift
;;
*)
echo "Unknown option: $arg"
echo "Usage: $0 [--charts-only]"
exit 1
;;
esac
done
# Build table list
TABLES=("${CORE_TABLES[@]}")
if [ "$RESTORE_DASHBOARDS" = true ]; then
TABLES+=("${DASHBOARD_TABLES[@]}")
fi
echo "Restoring the following tables in database '$DB_NAME':"
for table in "${TABLES[@]}"; do
echo " - $table"
done
echo ""
# Restore each table
for table in "${TABLES[@]}"; do
echo "Restoring table: $table"
# First, truncate the existing table (with CASCADE to handle foreign keys)
kubectl exec -n "$POSTGRES_NAMESPACE" "$POD_NAME" -- \
bash -c "PGPASSWORD='$DB_PASSWORD' psql -h localhost -U $DB_USER -d $DB_NAME -c 'TRUNCATE TABLE $table CASCADE;'" || {
echo "Warning: Failed to truncate $table (table might not exist yet)"
}
# Disable foreign key constraints temporarily
kubectl exec -n "$POSTGRES_NAMESPACE" "$POD_NAME" -- \
bash -c "PGPASSWORD='$DB_PASSWORD' psql -h localhost -U $DB_USER -d $DB_NAME -c 'ALTER TABLE $table DISABLE TRIGGER ALL;'" || {
echo "Warning: Failed to disable triggers on $table"
}
# Restore the table data (without --disable-triggers as we're managing it manually)
kubectl exec -n "$POSTGRES_NAMESPACE" "$POD_NAME" -- \
bash -c "PGPASSWORD='$DB_PASSWORD' pg_restore -h localhost -U $DB_USER -d $DB_NAME \
--table=$table \
--data-only \
$BACKUP_FILE" || {
echo "Error: Failed to restore $table"
exit 1
}
# Re-enable foreign key constraints
kubectl exec -n "$POSTGRES_NAMESPACE" "$POD_NAME" -- \
bash -c "PGPASSWORD='$DB_PASSWORD' psql -h localhost -U $DB_USER -d $DB_NAME -c 'ALTER TABLE $table ENABLE TRIGGER ALL;'" || {
echo "Warning: Failed to enable triggers on $table"
}
echo " ✓ Successfully restored $table"
done
echo ""
echo "Fixing orphaned user references..."
# Fix orphaned foreign key references to ab_user
kubectl exec -n "$POSTGRES_NAMESPACE" "$POD_NAME" -- \
bash -c "PGPASSWORD='$DB_PASSWORD' psql -h localhost -U $DB_USER -d $DB_NAME" <<'EOF'
BEGIN;
-- Temporarily disable triggers to allow updates
SET session_replication_role = replica;
-- Fix all orphaned references to ab_user (replace with user_id = 1)
UPDATE dashboards SET created_by_fk = 1 WHERE created_by_fk NOT IN (SELECT id FROM ab_user);
UPDATE dashboards SET changed_by_fk = 1 WHERE changed_by_fk NOT IN (SELECT id FROM ab_user);
UPDATE tables SET created_by_fk = 1 WHERE created_by_fk NOT IN (SELECT id FROM ab_user);
UPDATE tables SET changed_by_fk = 1 WHERE changed_by_fk NOT IN (SELECT id FROM ab_user);
UPDATE slices SET created_by_fk = 1 WHERE created_by_fk NOT IN (SELECT id FROM ab_user);
UPDATE slices SET changed_by_fk = 1 WHERE changed_by_fk NOT IN (SELECT id FROM ab_user);
UPDATE slices SET last_saved_by_fk = 1 WHERE last_saved_by_fk NOT IN (SELECT id FROM ab_user);
UPDATE sql_metrics SET created_by_fk = 1 WHERE created_by_fk NOT IN (SELECT id FROM ab_user);
UPDATE sql_metrics SET changed_by_fk = 1 WHERE changed_by_fk NOT IN (SELECT id FROM ab_user);
UPDATE table_columns SET created_by_fk = 1 WHERE created_by_fk NOT IN (SELECT id FROM ab_user);
UPDATE table_columns SET changed_by_fk = 1 WHERE changed_by_fk NOT IN (SELECT id FROM ab_user);
-- Re-enable triggers
SET session_replication_role = DEFAULT;
COMMIT;
EOF
echo " ✓ Successfully fixed orphaned user references"
echo ""
echo "Fixing PostgreSQL sequences..."
# Fix all sequences to prevent primary key conflicts
kubectl exec -n "$POSTGRES_NAMESPACE" "$POD_NAME" -- \
bash -c "PGPASSWORD='$DB_PASSWORD' psql -h localhost -U $DB_USER -d $DB_NAME" <<'EOF'
DO $$
DECLARE
r RECORD;
BEGIN
FOR r IN
SELECT s.relname AS seq_name, t.relname AS table_name
FROM pg_class AS s
JOIN pg_depend AS d ON d.objid = s.oid
JOIN pg_class AS t ON d.refobjid = t.oid
WHERE s.relkind = 'S'
AND t.relkind = 'r'
AND t.relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'public')
LOOP
EXECUTE format('SELECT setval(%L, (SELECT COALESCE(MAX(id), 0) + 1 FROM %I))', r.seq_name, r.table_name);
RAISE NOTICE 'Fixed sequence % for table %', r.seq_name, r.table_name;
END LOOP;
END $$;
EOF
echo " ✓ Successfully fixed all sequences"
echo ""
echo "Restoration completed successfully!"
echo ""
echo "Restored tables:"
for table in "${TABLES[@]}"; do
count=$(kubectl exec -n "$POSTGRES_NAMESPACE" "$POD_NAME" -- \
bash -c "PGPASSWORD='$DB_PASSWORD' psql -h localhost -U $DB_USER -d $DB_NAME -tAc 'SELECT COUNT(*) FROM $table;'")
echo " - $table: $count rows"
done