Files
langflow/.github/workflows/migration-validation.yml
Jordan Frazier 37cf0a14ed test: add upgrade migration check to ci (#12061)
* Add upgrade migration check to ci

* [autofix.ci] apply automated fixes

* Add fetch step

* ruff

* Add merge migration

* Revert "Add merge migration"

This reverts commit fd32424739.

backups

* coderabbit suggestions

  1. Shell hardening in workflow - set -euo pipefail, full path grep, quoted variables
  2. _WORKSPACE_ROOT extracted as module constant (also addresses Cristhianzl's review comment about parents[5] duplication)
  3. git missing returns None instead of raising FileNotFoundError
  4. # noqa: S603 added to subprocess.run (fixes the Ruff CI failure)
  5. FK noise filtering now also compares target table/column, not just ondelete/onupdate
  6. Removed redundant git fetch origin main step (fetch-depth: 0 already fetches all branches)
  7. Deduplicated Alembic config creation in _get_main_branch_head (moved before the if branch)
  8. Simplified dict type hints (removed unnecessary dict[tuple, object])

* test: improve migration tests from PR review feedback

- Narrow broad except clause to only wrap subprocess.run call
- Add specific error messages for multi-head and unresolvable revisions
- Remove redundant hardcoded schema test (covered by compare_metadata)
- Fix SQLite FK noise filter to skip ondelete/onupdate comparison
- Add downgrade verification to test_upgrade_from_main_branch
- Add test file and workflow to CI trigger paths
- Add prompt for follow-up PostgreSQL migration test PR

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* add engine check on downgrade

* [autofix.ci] apply automated fixes

* fix: harden CI error handling and test robustness

- Set validationPassed=false when validator crashes so CI fails instead of passing silently
- Wrap GitHub API calls in try-catch so comment-posting failures don't mask validation results
- Preserve git stderr in warnings for better CI debugging
- Add defensive handling for unexpected FK constraint shapes in SQLite noise filter
- Clean up SQLite WAL/SHM/journal companion files in test teardown

* Add explicit fetch to main

* ruff

* [autofix.ci] apply automated fixes

* Add sqlite filter tests and remove redundant fetch

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 15:55:51 +00:00

190 lines
6.9 KiB
YAML

name: Database Migration Validation
on:
pull_request:
paths:
- 'src/backend/base/langflow/alembic/versions/*.py'
- 'src/backend/base/langflow/services/database/models/**/*.py'
- 'src/backend/tests/unit/alembic/test_migration_execution.py'
- '.github/workflows/migration-validation.yml'
jobs:
model-migration-consistency:
name: Model/Migration Consistency
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install uv
uses: astral-sh/setup-uv@v6
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install dependencies
run: |
uv sync
- name: Check model/migration consistency
env:
MIGRATION_VALIDATION_CI: "true"
run: |
uv run pytest src/backend/tests/unit/alembic/test_migration_execution.py -x -v
validate-migration:
name: Migration Pattern Validation
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Get changed migration files
id: changed-files
run: |
set -euo pipefail
CHANGED_FILES=$(git diff --name-only origin/main...HEAD | grep -E 'src/backend/base/langflow/alembic/versions/.*\.py$' | grep -v 'test_migrations/' || echo "")
if [ -z "$CHANGED_FILES" ]; then
echo "No migration files changed"
echo "files=" >> "$GITHUB_OUTPUT"
else
echo "Changed migration files:"
echo "$CHANGED_FILES"
echo "files=$(printf '%s' "$CHANGED_FILES" | tr '\n' ' ')" >> "$GITHUB_OUTPUT"
fi
- name: Validate migration patterns
if: steps.changed-files.outputs.files != ''
env:
MIGRATION_FILES: ${{ steps.changed-files.outputs.files }}
run: |
python src/backend/base/langflow/alembic/migration_validator.py $MIGRATION_FILES
- name: Generate validation report
if: always() && steps.changed-files.outputs.files != ''
env:
MIGRATION_FILES: ${{ steps.changed-files.outputs.files }}
run: |
python src/backend/base/langflow/alembic/migration_validator.py \
--json $MIGRATION_FILES > validation-report.json 2> validation-stderr.txt || true
if [ ! -s validation-report.json ]; then
echo "::error::Validator produced no output. Stderr:"
cat validation-stderr.txt
fi
- name: Post PR comment with results
if: always() && steps.changed-files.outputs.files != ''
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
let message = '';
let validationPassed = true;
try {
const report = JSON.parse(fs.readFileSync('validation-report.json', 'utf8'));
for (const result of report) {
if (!result.valid) {
validationPassed = false;
}
}
if (validationPassed) {
message = `✅ **Migration Validation Passed**\n\n`;
message += `All migrations follow the Expand-Contract pattern correctly.\n\n`;
} else {
message = `❌ **Migration Validation Failed**\n\n`;
message += `Your migrations don't follow the Expand-Contract pattern.\n\n`;
for (const result of report) {
if (!result.valid || result.warnings.length > 0) {
message += `### File: \`${result.file.split('/').pop()}\`\n`;
message += `**Phase:** ${result.phase}\n\n`;
if (result.violations && result.violations.length > 0) {
message += `**Violations:**\n`;
for (const v of result.violations) {
message += `- Line ${v.line}: ${v.message}\n`;
}
message += `\n`;
}
if (result.warnings && result.warnings.length > 0) {
message += `**Warnings:**\n`;
for (const w of result.warnings) {
message += `- Line ${w.line}: ${w.message}\n`;
}
message += `\n`;
}
}
}
message += `### Resources\n`;
message += `- Review the [DB Migration Guide](./src/backend/base/langflow/alembic/DB-MIGRATION-GUIDE.MD)\n`;
message += `- Use \`python scripts/generate_migration.py --help\` to generate compliant migrations\n\n`;
message += `### Common Issues & Solutions\n`;
message += `- **New columns must be nullable:** Add \`nullable=True\` or \`server_default\`\n`;
message += `- **Missing phase marker:** Add \`Phase: EXPAND/MIGRATE/CONTRACT\` to docstring\n`;
message += `- **Column drops:** Only allowed in CONTRACT phase\n`;
message += `- **Direct renames:** Use expand-contract pattern instead\n`;
}
} catch (error) {
message = `⚠️ **Migration validation check failed to run properly**\n`;
message += `Error: ${error.message}\n`;
validationPassed = false;
}
// Post or update comment (non-critical — don't let API errors mask validation results)
try {
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const botComment = comments.find(comment =>
comment.user.type === 'Bot' &&
comment.body.includes('Migration Validation')
);
if (botComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: message
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: message
});
}
} catch (apiError) {
core.warning(`Failed to post PR comment: ${apiError.message}`);
}
// Fail the workflow if validation didn't pass
if (!validationPassed) {
core.setFailed('Migration validation failed');
}