Skip to content

Commit fdc59a3

Browse files
authored
Merge pull request #858 from DataIntegrationGroup/ci/report-pending-data-migrations
ci(staging): report data migrations that have not been applied
2 parents 44d7117 + c4f6dc5 commit fdc59a3

3 files changed

Lines changed: 104 additions & 0 deletions

File tree

.github/workflows/CD_staging.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,22 @@ jobs:
6363
run: |
6464
uv run --no-dev alembic upgrade head
6565
66+
# Data migrations are deliberately not applied here -- they change content
67+
# rather than structure, are often irreversible, and applying one is a
68+
# decision made in the Data Migrations workflow. This only reports, so a
69+
# merged migration cannot sit unnoticed. It never fails the deploy: the
70+
# deploy worked, and a pipeline that goes red for something else is a
71+
# pipeline people learn to ignore.
72+
- name: Report pending data migrations
73+
env:
74+
DB_DRIVER: "cloudsql"
75+
CLOUD_SQL_INSTANCE_NAME: "${{ secrets.CLOUD_SQL_INSTANCE_NAME }}"
76+
CLOUD_SQL_DATABASE: "${{ vars.CLOUD_SQL_DATABASE }}"
77+
CLOUD_SQL_USER: "${{ secrets.CLOUD_SQL_USER }}"
78+
CLOUD_SQL_IAM_AUTH: true
79+
run: |
80+
uv run --no-dev python -m scripts.report_pending_data_migrations
81+
6682
- name: Ensure envsubst is available
6783
run: |
6884
if ! command -v envsubst >/dev/null 2>&1; then

scripts/__init__.py

Whitespace-only changes.
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# ===============================================================================
2+
# Copyright 2026 ross
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
# ===============================================================================
16+
"""
17+
Report data migrations registered but not yet applied to this environment.
18+
19+
Deploys run `alembic upgrade head` and nothing else, deliberately: a data
20+
migration changes content rather than structure, is often irreversible -- a
21+
deletion has no downgrade -- and may be slow enough to hold a deploy hostage.
22+
Applying one is a decision, and `data_migrations.yml` is where it is made.
23+
24+
The cost of that choice is that a merged migration can sit unnoticed. This
25+
closes the gap without moving the decision: it reports, and never applies.
26+
27+
Exits zero even when migrations are pending. A deploy that succeeded should not
28+
report failure because a separate, deliberate action has not been taken yet --
29+
people learn to ignore a pipeline that cries wolf. The finding surfaces as a
30+
GitHub warning annotation and in the job summary instead.
31+
"""
32+
33+
import os
34+
35+
36+
def main() -> int:
37+
from data_migrations.runner import get_status
38+
from db.engine import session_ctx
39+
40+
try:
41+
with session_ctx() as session:
42+
statuses = get_status(session)
43+
except Exception as exc: # noqa: BLE001 - never fail a good deploy over this
44+
print(f"::warning::Could not read data migration status: {exc}")
45+
return 0
46+
47+
pending = [s for s in statuses if s.applied_count == 0 and not s.is_repeatable]
48+
applied = len(statuses) - len(pending)
49+
50+
summary = [
51+
"## Data migrations",
52+
"",
53+
f"{applied} applied, **{len(pending)} pending**.",
54+
"",
55+
]
56+
57+
if pending:
58+
for status in pending:
59+
print(
60+
f"::warning::Data migration not applied: {status.id} "
61+
f"({status.name}). Run it from the Data Migrations workflow."
62+
)
63+
summary += [
64+
"| id | name |",
65+
"| --- | --- |",
66+
*[f"| `{s.id}` | {s.name} |" for s in pending],
67+
"",
68+
"These do **not** run on deploy. Apply them from the "
69+
"**Data Migrations** workflow when you intend to.",
70+
]
71+
else:
72+
summary.append("Nothing pending.")
73+
74+
print("\n".join(summary))
75+
76+
path = os.environ.get("GITHUB_STEP_SUMMARY")
77+
if path:
78+
with open(path, "a") as handle:
79+
handle.write("\n".join(summary) + "\n")
80+
81+
return 0
82+
83+
84+
if __name__ == "__main__":
85+
raise SystemExit(main())
86+
87+
88+
# ============= EOF =============================================

0 commit comments

Comments
 (0)