57 lines
1.9 KiB
Python
57 lines
1.9 KiB
Python
"""Enforce the two fixed Node Agent credential scopes.
|
|
|
|
Revision ID: 20260830_0023
|
|
Revises: 20260828_0022
|
|
"""
|
|
|
|
from collections.abc import Sequence
|
|
|
|
import sqlalchemy as sa
|
|
|
|
from alembic import op
|
|
|
|
revision: str = "20260830_0023"
|
|
down_revision: str | None = "20260828_0022"
|
|
branch_labels: str | Sequence[str] | None = None
|
|
depends_on: str | Sequence[str] | None = None
|
|
|
|
|
|
_SCOPE_CONSTRAINTS = (
|
|
("node_enrollments", "node.enroll", "ck_node_enrollment_scope"),
|
|
("node_credentials", "node.publish", "ck_node_credential_scope"),
|
|
)
|
|
|
|
|
|
def _validate_existing_scopes() -> None:
|
|
connection = op.get_bind()
|
|
for table_name, expected_scope, _constraint_name in _SCOPE_CONSTRAINTS:
|
|
table = sa.table(table_name, sa.column("scope", sa.String(64)))
|
|
invalid_rows = connection.scalar(
|
|
sa.select(sa.func.count())
|
|
.select_from(table)
|
|
.where(sa.or_(table.c.scope.is_(None), table.c.scope != expected_scope))
|
|
)
|
|
if invalid_rows:
|
|
raise RuntimeError(
|
|
f"refusing node-scope migration: {table_name} contains "
|
|
f"{invalid_rows} row(s) outside {expected_scope!r}"
|
|
)
|
|
|
|
|
|
def upgrade() -> None:
|
|
# Validate every table before changing either one. A malformed production row therefore aborts
|
|
# the migration without leaving a partially hardened schema.
|
|
_validate_existing_scopes()
|
|
for table_name, expected_scope, constraint_name in _SCOPE_CONSTRAINTS:
|
|
with op.batch_alter_table(table_name) as batch_op:
|
|
batch_op.create_check_constraint(
|
|
constraint_name,
|
|
f"scope = '{expected_scope}'",
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
for table_name, _expected_scope, constraint_name in reversed(_SCOPE_CONSTRAINTS):
|
|
with op.batch_alter_table(table_name) as batch_op:
|
|
batch_op.drop_constraint(constraint_name, type_="check")
|