Skip to content

Commit dea49de

Browse files
authored
ci: add API and database Schema validation workflow (#9710)
1 parent 356c2f7 commit dea49de

File tree

5 files changed

+470
-0
lines changed

5 files changed

+470
-0
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
api-path-removed-without-deprecation: ERR
2+
api-path-removed-before-sunset: ERR
3+
api-removed-without-deprecation: ERR
4+
api-removed-before-sunset: ERR
5+
api-operation-id-removed: ERR
6+
api-tag-removed: WARN
7+
request-body-became-enum: ERR
8+
request-body-became-optional: INFO
9+
request-body-became-required: ERR
10+
request-body-max-length-decreased: ERR
11+
request-body-max-length-increased: INFO
12+
request-body-min-length-increased: ERR
13+
request-body-min-length-decreased: INFO
14+
request-body-type-changed: ERR
15+
request-property-became-not-nullable: ERR
16+
request-property-became-nullable: INFO
17+
request-property-became-optional: INFO
18+
request-property-became-required: ERR
19+
request-property-default-value-changed: WARN
20+
request-property-enum-value-removed: ERR
21+
request-property-enum-value-added: INFO
22+
request-property-max-decreased: ERR
23+
request-property-max-increased: INFO
24+
request-property-max-length-decreased: ERR
25+
request-property-max-length-increased: INFO
26+
request-property-min-increased: ERR
27+
request-property-min-decreased: INFO
28+
request-property-min-length-increased: ERR
29+
request-property-min-length-decreased: INFO
30+
request-property-pattern-changed: ERR
31+
request-property-pattern-removed: INFO
32+
request-property-removed: ERR
33+
request-property-type-changed: ERR
34+
request-property-x-extensible-enum-value-removed: ERR
35+
response-header-became-optional: ERR
36+
response-property-default-value-changed: WARN
37+
response-property-enum-value-added: INFO
38+
response-property-pattern-changed: ERR
39+
response-property-pattern-removed: INFO
40+
response-property-type-changed: ERR
41+
request-parameter-became-optional: INFO
42+
request-parameter-became-required: ERR
43+
request-parameter-default-value-changed: WARN
44+
request-parameter-enum-value-removed: ERR
45+
request-parameter-enum-value-added: INFO
46+
request-parameter-max-decreased: ERR
47+
request-parameter-max-increased: INFO
48+
request-parameter-max-length-decreased: ERR
49+
request-parameter-max-length-increased: INFO
50+
request-parameter-min-increased: ERR
51+
request-parameter-min-decreased: INFO
52+
request-parameter-min-length-increased: ERR
53+
request-parameter-min-length-decreased: INFO
54+
request-parameter-pattern-changed: ERR
55+
request-parameter-pattern-removed: INFO
56+
request-parameter-type-changed: ERR
57+
request-parameter-x-extensible-enum-value-removed: ERR
58+
request-parameter-deprecated: WARN
59+
api-schema-removed: INFO
60+
endpoint-deprecated: WARN
61+
endpoint-reactivated: INFO
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
response-property-max-length-increased
2+
response-property-min-length-decreased
3+
response-property-max-increased
4+
response-property-min-decreased
5+
request-parameter-max-length-increased
6+
request-parameter-min-length-decreased
7+
request-parameter-max-increased
8+
request-parameter-min-decreased
9+
api-tag-added
10+
response-property-enum-value-added
11+
request-property-enum-value-added
12+
request-parameter-enum-value-added
13+
response-media-type-added
14+
request-body-media-type-added
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Migration Rules Configuration
2+
# This file defines SQL patterns that are considered breaking changes or warnings
3+
# for database migrations, with folder-specific rule application.
4+
5+
# Define reusable rule patterns
6+
rule_patterns:
7+
drop_table:
8+
pattern: 'DROP\s+TABLE\s+'
9+
description: "Dropping a table removes data and breaks existing queries"
10+
11+
drop_column:
12+
pattern: 'DROP\s+COLUMN\s+'
13+
description: "Dropping a column breaks applications expecting it"
14+
15+
delete_data:
16+
pattern: 'DELETE\s+FROM\s+'
17+
description: "Deleting data can break application logic"
18+
19+
truncate_table:
20+
pattern: 'TRUNCATE\s+'
21+
description: "Truncating removes all data from a table"
22+
23+
rename_table:
24+
pattern: 'RENAME\s+TO\s+'
25+
description: "Renaming a table breaks existing queries and code references"
26+
27+
rename_column:
28+
pattern: 'RENAME\s+COLUMN\s+'
29+
description: "Renaming a column breaks applications using the old name"
30+
31+
set_not_null:
32+
pattern: 'ALTER\s+COLUMN\s+.*\s+SET\s+NOT\s+NULL'
33+
description: "Making an existing nullable column NOT NULL can fail with existing NULL data"
34+
35+
add_column_not_null_no_default:
36+
pattern: 'ALTER\s+TABLE\s+.*\s+ADD\s+COLUMN\s+(?!.*DEFAULT).*NOT\s+NULL'
37+
description: "Adding a NOT NULL column without a DEFAULT to an existing table will fail"
38+
39+
alter_column_type:
40+
pattern: '(ALTER\s+COLUMN\s+.*\s+TYPE|ALTER\s+COLUMN\s+.*\s+SET\s+DATA\s+TYPE)'
41+
description: "Changing column type may cause data loss or conversion issues"
42+
43+
drop_index:
44+
pattern: 'DROP\s+INDEX\s+'
45+
description: "Dropping an index can significantly impact query performance"
46+
47+
drop_constraint:
48+
pattern: 'DROP\s+CONSTRAINT\s+'
49+
description: "Dropping a constraint may allow invalid data"
50+
51+
alter_default:
52+
pattern: '(SET\s+DEFAULT|DROP\s+DEFAULT)'
53+
description: "Changing defaults affects new rows only, may cause inconsistency"
54+
55+
# Apply rules per migration folder
56+
# Each folder can have its own set of breaking and warning rules
57+
folder_rules:
58+
# V1 migrations
59+
migrations:
60+
breaking:
61+
- drop_table
62+
- drop_column
63+
- delete_data
64+
- truncate_table
65+
- rename_table
66+
- rename_column
67+
- set_not_null
68+
- add_column_not_null_no_default
69+
- alter_column_type
70+
warnings:
71+
- drop_index
72+
- drop_constraint
73+
- alter_default
74+
75+
# V2 migrations
76+
v2_migrations:
77+
breaking:
78+
- drop_table
79+
- drop_column
80+
- delete_data
81+
- truncate_table
82+
- rename_table
83+
- rename_column
84+
- set_not_null
85+
- add_column_not_null_no_default
86+
- alter_column_type
87+
warnings:
88+
- drop_index
89+
- drop_constraint
90+
- alter_default
91+
92+
# V2 compatible migrations
93+
v2_compatible_migrations:
94+
breaking:
95+
- drop_table
96+
- drop_column
97+
- delete_data
98+
- truncate_table
99+
- rename_table
100+
- rename_column
101+
- set_not_null
102+
- add_column_not_null_no_default
103+
- alter_column_type
104+
warnings:
105+
- drop_index
106+
- drop_constraint
107+
- alter_default
108+
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#!/bin/bash
2+
3+
# validate_migrations.sh
4+
# Validates database migrations for breaking changes and warnings
5+
# Usage: ./validate_migrations.sh <migration_folder>
6+
7+
set -euo pipefail
8+
9+
MIGRATION_FOLDER="${1:-migrations}"
10+
BASE_REF="${BASE_REF:-origin/main}"
11+
RULES_FILE="${RULES_FILE:-.github/api-migration-compatibility/migration-rules.yaml}"
12+
13+
echo "Validating migrations in: $MIGRATION_FOLDER"
14+
15+
# Check if migration directory exists
16+
if [[ ! -d "$MIGRATION_FOLDER" ]]; then
17+
echo "breaking_changes=0" >> "$GITHUB_OUTPUT"
18+
echo "warnings=0" >> "$GITHUB_OUTPUT"
19+
echo "Directory $MIGRATION_FOLDER does not exist, skipping validation"
20+
exit 0
21+
fi
22+
23+
# Find new migrations
24+
NEW_MIGRATIONS=$(git diff --name-only --diff-filter=AM "$BASE_REF"...HEAD -- "$MIGRATION_FOLDER/**/up.sql" 2>/dev/null || echo "")
25+
26+
if [[ -z "$NEW_MIGRATIONS" ]]; then
27+
echo "breaking_changes=0" >> "$GITHUB_OUTPUT"
28+
echo "warnings=0" >> "$GITHUB_OUTPUT"
29+
echo "No new migrations found in $MIGRATION_FOLDER"
30+
exit 0
31+
fi
32+
33+
# Extract breaking rule patterns
34+
BREAKING_PATTERNS=$(
35+
for rule in $(yq ".folder_rules.${MIGRATION_FOLDER}.breaking[]" "$RULES_FILE" 2>/dev/null || echo ""); do
36+
if [[ -n "$rule" ]]; then
37+
yq ".rule_patterns.$rule.pattern" "$RULES_FILE"
38+
fi
39+
done | paste -sd'|' -
40+
)
41+
42+
# Extract warning rule patterns
43+
WARNING_PATTERNS=$(
44+
for rule in $(yq ".folder_rules.${MIGRATION_FOLDER}.warnings[]" "$RULES_FILE" 2>/dev/null || echo ""); do
45+
if [[ -n "$rule" ]]; then
46+
yq ".rule_patterns.$rule.pattern" "$RULES_FILE"
47+
fi
48+
done | paste -sd'|' -
49+
)
50+
51+
BREAKING=0
52+
WARNINGS=0
53+
54+
while IFS= read -r file; do
55+
[[ -z "$file" ]] && continue
56+
57+
# Check for breaking changes
58+
if grep --ignore-case --extended-regexp --quiet "$BREAKING_PATTERNS" "$file" 2>/dev/null; then
59+
echo "BREAKING: $file"
60+
MATCH_COUNT=$(grep --ignore-case --count --extended-regexp "$BREAKING_PATTERNS" "$file" 2>/dev/null || echo "0")
61+
grep --ignore-case --line-number --extended-regexp "$BREAKING_PATTERNS" "$file" 2>/dev/null | sed 's/^/ /' || true
62+
BREAKING=$((BREAKING + MATCH_COUNT))
63+
fi
64+
65+
# Check for warnings
66+
if grep --ignore-case --extended-regexp --quiet "$WARNING_PATTERNS" "$file" 2>/dev/null; then
67+
echo "WARNING: $file"
68+
MATCH_COUNT=$(grep --ignore-case --count --extended-regexp "$WARNING_PATTERNS" "$file" 2>/dev/null || echo "0")
69+
grep --ignore-case --line-number --extended-regexp "$WARNING_PATTERNS" "$file" 2>/dev/null | sed 's/^/ /' || true
70+
WARNINGS=$((WARNINGS + MATCH_COUNT))
71+
fi
72+
done <<< "$NEW_MIGRATIONS"
73+
74+
echo "breaking_changes=$BREAKING" >> "$GITHUB_OUTPUT"
75+
echo "warnings=$WARNINGS" >> "$GITHUB_OUTPUT"
76+
77+
if [[ $BREAKING -gt 0 ]]; then
78+
echo "::error::Breaking changes detected in $MIGRATION_FOLDER database migrations."
79+
echo "::error::Found $BREAKING breaking change(s)."
80+
exit 1
81+
fi

0 commit comments

Comments
 (0)