261 lines
8.9 KiB
Bash
261 lines
8.9 KiB
Bash
#!/usr/bin/env bash
|
|
# Migration Validation Script
|
|
# Validates migration naming conventions, detects duplicates, and checks for issues.
|
|
#
|
|
# Usage:
|
|
# ./validate-migrations.sh [--strict] [--fix-scanner]
|
|
#
|
|
# Options:
|
|
# --strict Exit with error on any warning
|
|
# --fix-scanner Generate rename commands for Scanner duplicates
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
|
|
|
STRICT_MODE=false
|
|
FIX_SCANNER=false
|
|
EXIT_CODE=0
|
|
|
|
# Parse arguments
|
|
for arg in "$@"; do
|
|
case $arg in
|
|
--strict)
|
|
STRICT_MODE=true
|
|
shift
|
|
;;
|
|
--fix-scanner)
|
|
FIX_SCANNER=true
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
echo "=== Migration Validation ==="
|
|
echo "Repository: $REPO_ROOT"
|
|
echo ""
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
YELLOW='\033[1;33m'
|
|
GREEN='\033[0;32m'
|
|
NC='\033[0m' # No Color
|
|
|
|
# Track issues
|
|
ERRORS=()
|
|
WARNINGS=()
|
|
|
|
# Function to check for duplicates in a directory
|
|
check_duplicates() {
|
|
local dir="$1"
|
|
local module="$2"
|
|
|
|
if [ ! -d "$dir" ]; then
|
|
return
|
|
fi
|
|
|
|
# Extract numeric prefixes and find duplicates
|
|
local duplicates
|
|
duplicates=$(find "$dir" -maxdepth 1 -name "*.sql" -printf "%f\n" 2>/dev/null | \
|
|
sed -E 's/^([0-9]+)_.*/\1/' | \
|
|
sort | uniq -d)
|
|
|
|
if [ -n "$duplicates" ]; then
|
|
for prefix in $duplicates; do
|
|
local files
|
|
files=$(find "$dir" -maxdepth 1 -name "${prefix}_*.sql" -printf "%f\n" | tr '\n' ', ' | sed 's/,$//')
|
|
ERRORS+=("[$module] Duplicate prefix $prefix: $files")
|
|
done
|
|
fi
|
|
}
|
|
|
|
# Function to check naming convention
|
|
check_naming() {
|
|
local dir="$1"
|
|
local module="$2"
|
|
|
|
if [ ! -d "$dir" ]; then
|
|
return
|
|
fi
|
|
|
|
find "$dir" -maxdepth 1 -name "*.sql" -printf "%f\n" 2>/dev/null | while read -r file; do
|
|
# Check standard pattern: NNN_description.sql
|
|
if [[ "$file" =~ ^[0-9]{3}_[a-z0-9_]+\.sql$ ]]; then
|
|
continue # Valid standard
|
|
fi
|
|
# Check seed pattern: SNNN_description.sql
|
|
if [[ "$file" =~ ^S[0-9]{3}_[a-z0-9_]+\.sql$ ]]; then
|
|
continue # Valid seed
|
|
fi
|
|
# Check data migration pattern: DMNNN_description.sql
|
|
if [[ "$file" =~ ^DM[0-9]{3}_[a-z0-9_]+\.sql$ ]]; then
|
|
continue # Valid data migration
|
|
fi
|
|
# Check for Flyway-style
|
|
if [[ "$file" =~ ^V[0-9]+.*\.sql$ ]]; then
|
|
WARNINGS+=("[$module] Flyway-style naming: $file (consider NNN_description.sql)")
|
|
continue
|
|
fi
|
|
# Check for EF Core timestamp style
|
|
if [[ "$file" =~ ^[0-9]{14,}_.*\.sql$ ]]; then
|
|
WARNINGS+=("[$module] EF Core timestamp naming: $file (consider NNN_description.sql)")
|
|
continue
|
|
fi
|
|
# Check for 4-digit prefix
|
|
if [[ "$file" =~ ^[0-9]{4}_.*\.sql$ ]]; then
|
|
WARNINGS+=("[$module] 4-digit prefix: $file (standard is 3-digit NNN_description.sql)")
|
|
continue
|
|
fi
|
|
# Non-standard
|
|
WARNINGS+=("[$module] Non-standard naming: $file")
|
|
done
|
|
}
|
|
|
|
# Function to check for dangerous operations in startup migrations
|
|
check_dangerous_ops() {
|
|
local dir="$1"
|
|
local module="$2"
|
|
|
|
if [ ! -d "$dir" ]; then
|
|
return
|
|
fi
|
|
|
|
find "$dir" -maxdepth 1 -name "*.sql" -printf "%f\n" 2>/dev/null | while read -r file; do
|
|
local filepath="$dir/$file"
|
|
local prefix
|
|
prefix=$(echo "$file" | sed -E 's/^([0-9]+)_.*/\1/')
|
|
|
|
# Only check startup migrations (001-099)
|
|
if [[ "$prefix" =~ ^0[0-9]{2}$ ]] && [ "$prefix" -lt 100 ]; then
|
|
# Check for DROP TABLE without IF EXISTS
|
|
if grep -qE "DROP\s+TABLE\s+(?!IF\s+EXISTS)" "$filepath" 2>/dev/null; then
|
|
ERRORS+=("[$module] $file: DROP TABLE without IF EXISTS in startup migration")
|
|
fi
|
|
|
|
# Check for DROP COLUMN (breaking change in startup)
|
|
if grep -qiE "ALTER\s+TABLE.*DROP\s+COLUMN" "$filepath" 2>/dev/null; then
|
|
ERRORS+=("[$module] $file: DROP COLUMN in startup migration (should be release migration 100+)")
|
|
fi
|
|
|
|
# Check for TRUNCATE
|
|
if grep -qiE "^\s*TRUNCATE" "$filepath" 2>/dev/null; then
|
|
ERRORS+=("[$module] $file: TRUNCATE in startup migration")
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Scan all module migration directories
|
|
echo "Scanning migration directories..."
|
|
echo ""
|
|
|
|
# Define module migration paths
|
|
declare -A MIGRATION_PATHS
|
|
MIGRATION_PATHS=(
|
|
["Authority"]="src/Authority/__Libraries/StellaOps.Authority.Storage.Postgres/Migrations"
|
|
["Concelier"]="src/Concelier/__Libraries/StellaOps.Concelier.Storage.Postgres/Migrations"
|
|
["Excititor"]="src/Excititor/__Libraries/StellaOps.Excititor.Storage.Postgres/Migrations"
|
|
["Policy"]="src/Policy/__Libraries/StellaOps.Policy.Storage.Postgres/Migrations"
|
|
["Scheduler"]="src/Scheduler/__Libraries/StellaOps.Scheduler.Storage.Postgres/Migrations"
|
|
["Notify"]="src/Notify/__Libraries/StellaOps.Notify.Storage.Postgres/Migrations"
|
|
["Scanner"]="src/Scanner/__Libraries/StellaOps.Scanner.Storage/Postgres/Migrations"
|
|
["Scanner.Triage"]="src/Scanner/__Libraries/StellaOps.Scanner.Triage/Migrations"
|
|
["Attestor"]="src/Attestor/__Libraries/StellaOps.Attestor.Persistence/Migrations"
|
|
["Signer"]="src/Signer/__Libraries/StellaOps.Signer.KeyManagement/Migrations"
|
|
["Signals"]="src/Signals/StellaOps.Signals.Storage.Postgres/Migrations"
|
|
["EvidenceLocker"]="src/EvidenceLocker/StellaOps.EvidenceLocker/StellaOps.EvidenceLocker.Infrastructure/Db/Migrations"
|
|
["ExportCenter"]="src/ExportCenter/StellaOps.ExportCenter/StellaOps.ExportCenter.Infrastructure/Db/Migrations"
|
|
["IssuerDirectory"]="src/IssuerDirectory/StellaOps.IssuerDirectory/StellaOps.IssuerDirectory.Storage.Postgres/Migrations"
|
|
["Orchestrator"]="src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Infrastructure/migrations"
|
|
["TimelineIndexer"]="src/TimelineIndexer/StellaOps.TimelineIndexer/StellaOps.TimelineIndexer.Infrastructure/Db/Migrations"
|
|
["BinaryIndex"]="src/BinaryIndex/__Libraries/StellaOps.BinaryIndex.Persistence/Migrations"
|
|
["Unknowns"]="src/Unknowns/__Libraries/StellaOps.Unknowns.Storage.Postgres/Migrations"
|
|
["VexHub"]="src/VexHub/__Libraries/StellaOps.VexHub.Storage.Postgres/Migrations"
|
|
)
|
|
|
|
for module in "${!MIGRATION_PATHS[@]}"; do
|
|
path="$REPO_ROOT/${MIGRATION_PATHS[$module]}"
|
|
if [ -d "$path" ]; then
|
|
echo "Checking: $module"
|
|
check_duplicates "$path" "$module"
|
|
check_naming "$path" "$module"
|
|
check_dangerous_ops "$path" "$module"
|
|
fi
|
|
done
|
|
|
|
echo ""
|
|
|
|
# Report errors
|
|
if [ ${#ERRORS[@]} -gt 0 ]; then
|
|
echo -e "${RED}=== ERRORS (${#ERRORS[@]}) ===${NC}"
|
|
for error in "${ERRORS[@]}"; do
|
|
echo -e "${RED} ✗ $error${NC}"
|
|
done
|
|
EXIT_CODE=1
|
|
echo ""
|
|
fi
|
|
|
|
# Report warnings
|
|
if [ ${#WARNINGS[@]} -gt 0 ]; then
|
|
echo -e "${YELLOW}=== WARNINGS (${#WARNINGS[@]}) ===${NC}"
|
|
for warning in "${WARNINGS[@]}"; do
|
|
echo -e "${YELLOW} ⚠ $warning${NC}"
|
|
done
|
|
if [ "$STRICT_MODE" = true ]; then
|
|
EXIT_CODE=1
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
# Scanner fix suggestions
|
|
if [ "$FIX_SCANNER" = true ]; then
|
|
echo "=== Scanner Migration Rename Suggestions ==="
|
|
echo "# Run these commands to fix Scanner duplicate migrations:"
|
|
echo ""
|
|
|
|
SCANNER_DIR="$REPO_ROOT/src/Scanner/__Libraries/StellaOps.Scanner.Storage/Postgres/Migrations"
|
|
if [ -d "$SCANNER_DIR" ]; then
|
|
# Map old names to new sequential numbers
|
|
cat << 'EOF'
|
|
# Before running: backup the schema_migrations table!
|
|
# After renaming: update schema_migrations.migration_name to match new names
|
|
|
|
cd src/Scanner/__Libraries/StellaOps.Scanner.Storage/Postgres/Migrations
|
|
|
|
# Fix duplicate 009 prefixes
|
|
git mv 009_call_graph_tables.sql 020_call_graph_tables.sql
|
|
git mv 009_smart_diff_tables_search_path.sql 021_smart_diff_tables_search_path.sql
|
|
|
|
# Fix duplicate 010 prefixes
|
|
git mv 010_reachability_drift_tables.sql 022_reachability_drift_tables.sql
|
|
git mv 010_scanner_api_ingestion.sql 023_scanner_api_ingestion.sql
|
|
git mv 010_smart_diff_priority_score_widen.sql 024_smart_diff_priority_score_widen.sql
|
|
|
|
# Fix duplicate 014 prefixes
|
|
git mv 014_epss_triage_columns.sql 025_epss_triage_columns.sql
|
|
git mv 014_vuln_surfaces.sql 026_vuln_surfaces.sql
|
|
|
|
# Renumber subsequent migrations
|
|
git mv 011_epss_raw_layer.sql 027_epss_raw_layer.sql
|
|
git mv 012_epss_signal_layer.sql 028_epss_signal_layer.sql
|
|
git mv 013_witness_storage.sql 029_witness_storage.sql
|
|
git mv 015_vuln_surface_triggers_update.sql 030_vuln_surface_triggers_update.sql
|
|
git mv 016_reach_cache.sql 031_reach_cache.sql
|
|
git mv 017_idempotency_keys.sql 032_idempotency_keys.sql
|
|
git mv 018_binary_evidence.sql 033_binary_evidence.sql
|
|
git mv 019_func_proof_tables.sql 034_func_proof_tables.sql
|
|
EOF
|
|
fi
|
|
echo ""
|
|
fi
|
|
|
|
# Summary
|
|
if [ $EXIT_CODE -eq 0 ]; then
|
|
echo -e "${GREEN}=== VALIDATION PASSED ===${NC}"
|
|
else
|
|
echo -e "${RED}=== VALIDATION FAILED ===${NC}"
|
|
fi
|
|
|
|
exit $EXIT_CODE
|