fix: commit migration role assumption
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 41s
All checks were successful
Build and Publish Container Image / build-and-push (push) Successful in 41s
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02b22-9638-76d2-bbff-b7ea1770b118
This commit is contained in:
parent
9751927d38
commit
b1fd3ec131
4 changed files with 32 additions and 3 deletions
|
|
@ -6,7 +6,7 @@ from alembic import context
|
|||
from sqlalchemy import engine_from_config, pool
|
||||
|
||||
from sbom_nexus.config import database_target, migration_role
|
||||
from sbom_nexus.database import database_url, metadata
|
||||
from sbom_nexus.database import assume_migration_role, database_url, metadata
|
||||
|
||||
config = context.config
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ def run_migrations_online() -> None:
|
|||
if role and connection.dialect.name != "postgresql":
|
||||
raise RuntimeError("SBOM_NEXUS_MIGRATION_ROLE requires PostgreSQL")
|
||||
if role:
|
||||
connection.exec_driver_sql(f'SET ROLE "{role}"')
|
||||
assume_migration_role(connection, role)
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ from sqlalchemy import (
|
|||
create_engine,
|
||||
event,
|
||||
)
|
||||
from sqlalchemy.engine import Engine
|
||||
from sqlalchemy.engine import Connection, Engine
|
||||
|
||||
NAMING_CONVENTION = {
|
||||
"ix": "ix_%(table_name)s_%(column_0_name)s",
|
||||
|
|
@ -130,6 +130,15 @@ def database_url(value: str | Path) -> str:
|
|||
return text
|
||||
|
||||
|
||||
def assume_migration_role(connection: Connection, role: str) -> None:
|
||||
"""Assume the durable owner without leaving Alembic inside an outer txn."""
|
||||
connection.exec_driver_sql(f'SET ROLE "{role}"')
|
||||
# SQLAlchemy 2 autobegins on SET ROLE. Commit that transaction before
|
||||
# Alembic opens its own so the migration transaction is not rolled back
|
||||
# when the connection closes. SET ROLE remains active for the session.
|
||||
connection.commit()
|
||||
|
||||
|
||||
def _dynamic_connection_factory(url_file: Path):
|
||||
def connect():
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -5,6 +5,21 @@ from pathlib import Path
|
|||
from sbom_nexus import database
|
||||
|
||||
|
||||
def test_migration_role_commits_autobegin_before_alembic_transaction() -> None:
|
||||
events: list[str] = []
|
||||
|
||||
class Connection:
|
||||
def exec_driver_sql(self, statement: str) -> None:
|
||||
events.append(statement)
|
||||
|
||||
def commit(self) -> None:
|
||||
events.append("COMMIT")
|
||||
|
||||
database.assume_migration_role(Connection(), "sbom_nexus_owner")
|
||||
|
||||
assert events == ['SET ROLE "sbom_nexus_owner"', "COMMIT"]
|
||||
|
||||
|
||||
def test_dynamic_connection_factory_rereads_mounted_url(monkeypatch, tmp_path: Path) -> None:
|
||||
secret = tmp_path / "url"
|
||||
observed: list[str] = []
|
||||
|
|
|
|||
|
|
@ -37,6 +37,11 @@ replaced with the current mounted credential.
|
|||
Completed with tests proving two connection attempts observe two file values
|
||||
and that the engine URL contains neither username nor password.
|
||||
|
||||
The first live promotion also exposed an Alembic `SET ROLE` autobegin defect:
|
||||
the migration logged success but its outer transaction rolled back on close.
|
||||
The migration role helper now commits only the role-assumption transaction
|
||||
before Alembic starts its own, with an ordering regression test.
|
||||
|
||||
## Separate liveness from database readiness
|
||||
|
||||
```task
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue