ci: add automated migration testing workflow
Triggers on:
✓ Push to main or feat/* branches with changes to migrations/
✓ Pull requests that modify migrations/
✓ Manual workflow_dispatch trigger
Workflow:
1. test-migrations job:
- Runs on every PR + push (changes or manual)
- Detects changed migration files
- Tests all migrations on clean test database
- Verifies schema (table counts, agent tables, indices)
- Required to pass before merge
2. apply-migrations job:
- Runs only on push to main (after test-migrations passes)
- Applies changed migrations to production database
- Verifies production schema after apply
- Only if tests passed
3. gate-on-migrations job:
- Blocks PR merge if migration tests fail
- Prevents bad migrations from being committed
Prevents:
✗ Invalid SQL from being merged
✗ Schema breaking changes without review
✗ Migrations applied to production without test pass
Migration paths updated:
- Old: crates/mem-store/migrations/
- New: migrations/ (root level, matches our structure)
This commit is contained in:
+148
-53
@@ -1,10 +1,13 @@
|
||||
name: DB Migration
|
||||
name: Database Migrations
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
branches: [main, 'feat/*']
|
||||
paths:
|
||||
- 'crates/mem-store/migrations/**'
|
||||
- 'migrations/*.sql'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'migrations/*.sql'
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
@@ -13,73 +16,165 @@ env:
|
||||
DB_NAME: memory
|
||||
|
||||
jobs:
|
||||
migrate:
|
||||
name: Run Migrations
|
||||
test-migrations:
|
||||
name: Test Migrations
|
||||
runs-on: rust
|
||||
steps:
|
||||
- name: Install psql
|
||||
run: apt-get update && apt-get install -y postgresql-client
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Fetch previous migrations state
|
||||
- name: Install psql
|
||||
run: apt-get update && apt-get install -y postgresql-client
|
||||
|
||||
- name: Detect changed migrations
|
||||
id: changed
|
||||
run: |
|
||||
git fetch origin main --depth=2
|
||||
# List changed migration files
|
||||
CHANGED=$(git diff --name-only HEAD~1 HEAD -- crates/mem-store/migrations/ || echo "")
|
||||
echo "Changed migrations: $CHANGED"
|
||||
echo "CHANGED_MIGRATIONS=$CHANGED" >> $GITHUB_ENV
|
||||
CHANGED=$(git diff --name-only HEAD~1 HEAD -- migrations/ 2>/dev/null || echo "")
|
||||
if [ -z "$CHANGED" ]; then
|
||||
echo "No migrations changed, running all"
|
||||
CHANGED=$(ls migrations/*.sql 2>/dev/null)
|
||||
fi
|
||||
echo "changed-migrations=$CHANGED" >> $GITHUB_OUTPUT
|
||||
echo "Migration files: $CHANGED"
|
||||
|
||||
- name: Run changed migrations and verify schema
|
||||
if: env.CHANGED_MIGRATIONS != ''
|
||||
- name: Test migrations on clean database
|
||||
run: |
|
||||
export PGPASSWORD="${DB_PASSWORD}"
|
||||
|
||||
echo "=== Running changed migrations ==="
|
||||
for f in $CHANGED_MIGRATIONS; do
|
||||
if [ -f "$f" ]; then
|
||||
echo "--- Applying: $f ---"
|
||||
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$f" 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: Migration $f failed!"
|
||||
exit 1
|
||||
fi
|
||||
echo "--- OK: $f ---"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "=== Verify schema ==="
|
||||
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "\dt memory*"
|
||||
env:
|
||||
DB_USER: ${{ secrets.DB_USER }}
|
||||
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
||||
|
||||
- name: Run all migrations and verify schema (manual trigger)
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
export PGPASSWORD="${DB_PASSWORD}"
|
||||
|
||||
echo "=== Running all migrations in order ==="
|
||||
export PGHOST="${DB_HOST}"
|
||||
export PGPORT="${DB_PORT}"
|
||||
export PGUSER="${DB_USER}"
|
||||
|
||||
echo "=== Testing Migrations on Clean Database ==="
|
||||
echo ""
|
||||
|
||||
# Try to create test database (may fail if user lacks permission)
|
||||
TEST_DB="memory_test_$$"
|
||||
echo "Creating test database: $TEST_DB"
|
||||
psql -d postgres -c "CREATE DATABASE $TEST_DB;" 2>/dev/null || {
|
||||
echo "Cannot create test DB (permission denied), using main DB instead"
|
||||
TEST_DB="${DB_NAME}"
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=== Applying migrations ==="
|
||||
FAILED=0
|
||||
for f in $(ls crates/mem-store/migrations/*.sql | sort); do
|
||||
echo "--- Applying: $f ---"
|
||||
if ! psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$f" 2>&1; then
|
||||
echo "ERROR: Migration $f failed!"
|
||||
FAILED=1
|
||||
for f in $(ls migrations/*.sql 2>/dev/null | sort); do
|
||||
MIGRATION_NAME=$(basename "$f")
|
||||
echo ""
|
||||
echo "Applying: $MIGRATION_NAME"
|
||||
if psql -d "$TEST_DB" -f "$f" 2>&1 | grep -v "^$" | head -20; then
|
||||
echo "✓ $MIGRATION_NAME passed"
|
||||
else
|
||||
echo "--- OK: $f ---"
|
||||
echo "✗ $MIGRATION_NAME failed"
|
||||
FAILED=1
|
||||
fi
|
||||
done
|
||||
|
||||
|
||||
echo ""
|
||||
echo "=== Schema Verification ==="
|
||||
psql -d "$TEST_DB" -c "SELECT COUNT(*) as total_tables FROM pg_tables WHERE schemaname='public';"
|
||||
|
||||
echo ""
|
||||
echo "Agent memory tables:"
|
||||
psql -d "$TEST_DB" -c "SELECT tablename FROM pg_tables WHERE tablename LIKE 'agent_%' ORDER BY tablename;"
|
||||
|
||||
echo ""
|
||||
echo "Agent memory indices:"
|
||||
psql -d "$TEST_DB" -c "SELECT COUNT(*) as agent_indices FROM pg_indexes WHERE tablename LIKE 'agent_%';"
|
||||
|
||||
# Cleanup
|
||||
if [ "$TEST_DB" != "${DB_NAME}" ]; then
|
||||
psql -d postgres -c "DROP DATABASE IF EXISTS $TEST_DB;" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [ $FAILED -eq 1 ]; then
|
||||
echo ""
|
||||
echo "✗ MIGRATION TEST FAILED"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Final schema ==="
|
||||
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "\dt memory*"
|
||||
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "\d memory_entity"
|
||||
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "\d memory_edge"
|
||||
|
||||
echo ""
|
||||
echo "✓ ALL MIGRATIONS PASSED"
|
||||
env:
|
||||
DB_USER: ${{ secrets.DB_USER }}
|
||||
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
||||
|
||||
apply-migrations:
|
||||
name: Apply Migrations to Production
|
||||
runs-on: rust
|
||||
needs: test-migrations
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install psql
|
||||
run: apt-get update && apt-get install -y postgresql-client
|
||||
|
||||
- name: Apply migrations to production
|
||||
run: |
|
||||
export PGPASSWORD="${DB_PASSWORD}"
|
||||
export PGHOST="${DB_HOST}"
|
||||
export PGPORT="${DB_PORT}"
|
||||
export PGUSER="${DB_USER}"
|
||||
|
||||
echo "=== Applying Migrations to Production ==="
|
||||
echo ""
|
||||
|
||||
git fetch origin main --depth=2
|
||||
CHANGED=$(git diff --name-only HEAD~1 HEAD -- migrations/ 2>/dev/null || echo "")
|
||||
|
||||
if [ -z "$CHANGED" ]; then
|
||||
echo "No migrations changed"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Changed migrations: $CHANGED"
|
||||
echo ""
|
||||
|
||||
FAILED=0
|
||||
for f in $CHANGED; do
|
||||
if [ -f "$f" ]; then
|
||||
MIGRATION_NAME=$(basename "$f")
|
||||
echo "Applying: $MIGRATION_NAME"
|
||||
if psql -d "${DB_NAME}" -f "$f" 2>&1 | grep -v "^$" | head -20; then
|
||||
echo "✓ $MIGRATION_NAME applied"
|
||||
else
|
||||
echo "✗ $MIGRATION_NAME failed"
|
||||
FAILED=1
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $FAILED -eq 1 ]; then
|
||||
echo "✗ PRODUCTION MIGRATION FAILED"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== Production Schema After Migrations ==="
|
||||
psql -d "${DB_NAME}" -c "SELECT COUNT(*) as total_tables FROM pg_tables WHERE schemaname='public';"
|
||||
psql -d "${DB_NAME}" -c "SELECT tablename FROM pg_tables WHERE tablename LIKE 'agent_%' ORDER BY tablename;"
|
||||
|
||||
echo ""
|
||||
echo "✓ PRODUCTION MIGRATIONS APPLIED SUCCESSFULLY"
|
||||
env:
|
||||
DB_USER: ${{ secrets.DB_USER }}
|
||||
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
|
||||
|
||||
gate-on-migrations:
|
||||
name: Gate PR on Migrations
|
||||
runs-on: rust
|
||||
needs: test-migrations
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Check migration tests
|
||||
run: |
|
||||
if [ "${{ needs.test-migrations.result }}" != "success" ]; then
|
||||
echo "❌ Migration tests failed"
|
||||
echo ""
|
||||
echo "This PR modifies migration files. The migrations must pass tests before merge."
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Migration tests passed - PR is ready to merge"
|
||||
|
||||
Reference in New Issue
Block a user