Skip to content

Commit

Permalink
Use Alembic support code from Safir
Browse files Browse the repository at this point in the history
Remove the Alembic support code that has been lifted into Safir
and call the functions in Safir instead. Depend on Safir 6.4.0,
which added the Alembic support.
  • Loading branch information
rra committed Sep 16, 2024
1 parent 6fe133d commit 5f73719
Show file tree
Hide file tree
Showing 15 changed files with 217 additions and 562 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ repos:
- id: trailing-whitespace

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.4
rev: v0.6.5
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
Expand Down
189 changes: 10 additions & 179 deletions alembic/env.py
Original file line number Diff line number Diff line change
@@ -1,192 +1,23 @@
"""Alembic migration environment."""

import asyncio
import logging
from urllib.parse import quote, urlparse

import structlog
from alembic import context
from safir.database import create_database_engine
from safir.logging import LogLevel, add_log_severity
from sqlalchemy.engine import Connection
from safir.database import run_migrations_offline, run_migrations_online
from safir.logging import configure_alembic_logging

from gafaelfawr.dependencies.config import config_dependency
from gafaelfawr.schema import Base

# This is the Alembic Config object, which provides access to the values
# within the .ini file in use.
config = context.config

# Load the Gafaelfawr configuration, which as a side effect also configures
# logging using structlog.
gafaelfawr_config = config_dependency.config()

# Define the SQLAlchemy schema, which enables autogenerate support.
target_metadata = Base.metadata


def build_database_url(
url: str, password: str | None, *, is_async: bool
) -> str:
"""Build the authenticated URL for the database.
Parameters
----------
url
Database connection URL, not including the password.
password
Database connection password.
is_async
Whether the resulting URL should be async or not.
Returns
-------
url
The URL including the password.
Raises
------
ValueError
A password was provided but the connection URL has no username.
Notes
-----
This is duplicated from safir.database and should be replaced with an
exported Safir function once Safir provides one.
"""
if is_async or password:
parsed_url = urlparse(url)
if is_async and parsed_url.scheme == "postgresql":
parsed_url = parsed_url._replace(scheme="postgresql+asyncpg")
if password:
if not parsed_url.username:
raise ValueError(f"No username in database URL {url}")
password = quote(password, safe="")

# The username portion of the parsed URL does not appear to decode
# URL escaping of the username, so we should not quote it again or
# we will get double-quoting.
netloc = f"{parsed_url.username}:{password}@{parsed_url.hostname}"
if parsed_url.port:
netloc = f"{netloc}:{parsed_url.port}"
parsed_url = parsed_url._replace(netloc=netloc)
url = parsed_url.geturl()
return url


def configure_alembic_logging(
log_level: LogLevel | str = LogLevel.INFO,
) -> None:
"""Set up logging for Alembic.
This configures Alembic to use structlog for output formatting so that its
logs are also in JSON. This helps Google's Cloud Logging system understand
the logs.
Parameters
----------
log_level
The Python log level. May be given as a `LogLevel` enum (preferred)
or a case-insensitive string.
"""
if not isinstance(log_level, LogLevel):
log_level = LogLevel[log_level.upper()]

processors = [
structlog.stdlib.ProcessorFormatter.remove_processors_meta,
structlog.processors.format_exc_info,
structlog.processors.JSONRenderer(),
]
logging.config.dictConfig(
{
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {
"()": structlog.stdlib.ProcessorFormatter,
"processors": processors,
"foreign_pre_chain": [add_log_severity],
},
},
"handlers": {
"alembic": {
"level": log_level.value,
"class": "logging.StreamHandler",
"formatter": "json",
"stream": "ext://sys.stdout",
},
},
"loggers": {
"alembic": {
"handlers": ["alembic"],
"level": log_level.value,
"propagate": False,
},
},
}
)


def run_migrations_offline() -> None:
"""Run migrations in offline mode.
This configures the context with just a URL and not an Engine, though an
Engine is acceptable here as well. By skipping the Engine creation we
don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the script
output.
"""
url = build_database_url(
str(gafaelfawr_config.database_url),
gafaelfawr_config.database_password.get_secret_value(),
is_async=False,
)
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)

with context.begin_transaction():
context.run_migrations()


def do_run_migrations(connection: Connection) -> None:
"""Run database migrations with a connection."""
context.configure(connection=connection, target_metadata=target_metadata)
with context.begin_transaction():
context.run_migrations()


async def run_async_migrations() -> None:
"""Run migrations in online mode with an async engine.
In this scenario we need to create an Engine and associate a connection
with the context.
"""
engine = create_database_engine(
gafaelfawr_config.database_url,
gafaelfawr_config.database_password.get_secret_value(),
)

async with engine.connect() as connection:
await connection.run_sync(do_run_migrations)

await engine.dispose()


def run_migrations_online() -> None:
"""Run database migrations.
This must be called outside of an event loop.
"""
asyncio.run(run_async_migrations())

config = config_dependency.config()

# Run the migrations.
configure_alembic_logging()
if context.is_offline_mode():
run_migrations_offline()
run_migrations_offline(Base.metadata, config.database_url)
else:
run_migrations_online()
run_migrations_online(
Base.metadata,
config.database_url,
config.database_password,
)
2 changes: 1 addition & 1 deletion docs/user-guide/gafaelfawringress.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ The same token will also still be passed in the ``X-Auth-Request-Token`` header.
If this configuration option is set, the incoming ``Authorization`` header will be entirely replaced by one containing only the delegated token, unlike Gafaelfawr's normal behavior of preserving any incoming ``Authorization`` header that doesn't include a Gafaelfawr token.

Caching
=======
========

By default, Gafaelfawr is consulted for every HTTP request handled by the NGINX ingress.

Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/helm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,7 @@ See :ref:`client-ips` for more details.
.. _config-metrics:

Metrics
=======
========

Gafaelfawr can export metrics to an OpenTelemetry_ collector.
Currently, it only supports the insecure gRPC mechanism for sending metrics, and therefore should use a collector within the same Kubernetes cluster.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ dependencies = [
"pyjwt",
"pyyaml",
"redis>=4.2.0",
"safir[db,kubernetes]>=6.2.0",
"safir[db,kubernetes]>=6.4.0",
"sqlalchemy>=2.0.0",
"structlog",
]
Expand Down
82 changes: 17 additions & 65 deletions requirements/dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -630,9 +630,9 @@ hyperframe==6.0.1 \
# via
# h2
# selenium-wire
idna==3.8 \
--hash=sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac \
--hash=sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603
idna==3.10 \
--hash=sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9 \
--hash=sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3
# via
# -c requirements/main.txt
# anyio
Expand Down Expand Up @@ -889,9 +889,9 @@ pexpect==4.9.0 ; sys_platform != 'emscripten' and sys_platform != 'win32' \
--hash=sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523 \
--hash=sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f
# via ipython
platformdirs==4.3.2 \
--hash=sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c \
--hash=sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617
platformdirs==4.3.3 \
--hash=sha256:50a5450e2e84f44539718293cbb1da0a0885c9d14adf21b77bae4e66fc99d9b5 \
--hash=sha256:d4e0b7d8ec176b341fb03cb11ca12d0276faa8c485f9cd218f613840463fc2c0
# via jupyter-core
pluggy==1.5.0 \
--hash=sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1 \
Expand Down Expand Up @@ -1452,9 +1452,9 @@ selenium-wire==5.1.0 \
--hash=sha256:b1cd4eae44d9959381abe3bb186472520d063c658e279f98555def3d4e6dd29b \
--hash=sha256:fbf930d9992f8b6d24bb16b3e6221bab596a41f6ae7548270b7d5a92f3402622
# via -r requirements/dev.in
setuptools==74.1.2 \
--hash=sha256:5f4c08aa4d3ebcb57a50c33b1b07e94315d7fc7230f7115e47fc99776c8ce308 \
--hash=sha256:95b40ed940a1c67eb70fc099094bd6e99c6ee7c23aa2306f4d2697ba7916f9c6
setuptools==75.1.0 \
--hash=sha256:35ab7fd3bcd95e6b7fd704e4a1539513edad446c097797f2985e0e4b960772f2 \
--hash=sha256:d59a21b17a275fb872a9c3dae73963160ae079f1049ed956880cd7c09b120538
# via
# documenteer
# sphinxcontrib-bibtex
Expand Down Expand Up @@ -1518,9 +1518,9 @@ sphinx-autodoc-typehints==2.4.1 \
--hash=sha256:af37abb816ebd2cf56c7a8174fd2f34d0f2f84fbf58265f89429ae107212fe6f \
--hash=sha256:cfe410920cecf08ade046bb387b0007edb83e992de59686c62d194c762f1e45c
# via documenteer
sphinx-automodapi==0.17.0 \
--hash=sha256:4d029cb79eef29413e94ab01bb0177ebd2d5ba86e9789b73575afe9c06ae1501 \
--hash=sha256:7ccdadad57add4aa9149d9f2bb5cf28c8f8b590280b4735b1156ea8355c423a1
sphinx-automodapi==0.18.0 \
--hash=sha256:022860385590768f52d4f6e19abb83b2574772d2721fb4050ecdb6e593a1a440 \
--hash=sha256:7bf9d9a2cb67a5389c51071cfd86674ca3892ca5d5943f95de4553d6f35dddae
# via documenteer
sphinx-click==6.0.0 \
--hash=sha256:1e0a3c83bcb7c55497751b19d07ebe56b5d7b85eb76dd399cf9061b497adc317 \
Expand Down Expand Up @@ -1599,56 +1599,8 @@ sphinxext-rediraffe==0.2.7 \
--hash=sha256:651dcbfae5ffda9ffd534dfb8025f36120e5efb6ea1a33f5420023862b9f725d \
--hash=sha256:9e430a52d4403847f4ffb3a8dd6dfc34a9fe43525305131f52ed899743a5fd8c
# via documenteer
sqlalchemy==2.0.34 \
--hash=sha256:10d8f36990dd929690666679b0f42235c159a7051534adb135728ee52828dd22 \
--hash=sha256:13be2cc683b76977a700948411a94c67ad8faf542fa7da2a4b167f2244781cf3 \
--hash=sha256:165bbe0b376541092bf49542bd9827b048357f4623486096fc9aaa6d4e7c59a2 \
--hash=sha256:173f5f122d2e1bff8fbd9f7811b7942bead1f5e9f371cdf9e670b327e6703ebd \
--hash=sha256:196958cde924a00488e3e83ff917be3b73cd4ed8352bbc0f2989333176d1c54d \
--hash=sha256:203d46bddeaa7982f9c3cc693e5bc93db476ab5de9d4b4640d5c99ff219bee8c \
--hash=sha256:220574e78ad986aea8e81ac68821e47ea9202b7e44f251b7ed8c66d9ae3f4278 \
--hash=sha256:243f92596f4fd4c8bd30ab8e8dd5965afe226363d75cab2468f2c707f64cd83b \
--hash=sha256:24af3dc43568f3780b7e1e57c49b41d98b2d940c1fd2e62d65d3928b6f95f021 \
--hash=sha256:25691f4adfb9d5e796fd48bf1432272f95f4bbe5f89c475a788f31232ea6afba \
--hash=sha256:2e6965346fc1491a566e019a4a1d3dfc081ce7ac1a736536367ca305da6472a8 \
--hash=sha256:3166dfff2d16fe9be3241ee60ece6fcb01cf8e74dd7c5e0b64f8e19fab44911b \
--hash=sha256:413c85cd0177c23e32dee6898c67a5f49296640041d98fddb2c40888fe4daa2e \
--hash=sha256:430093fce0efc7941d911d34f75a70084f12f6ca5c15d19595c18753edb7c33b \
--hash=sha256:43f28005141165edd11fbbf1541c920bd29e167b8bbc1fb410d4fe2269c1667a \
--hash=sha256:526ce723265643dbc4c7efb54f56648cc30e7abe20f387d763364b3ce7506c82 \
--hash=sha256:53e68b091492c8ed2bd0141e00ad3089bcc6bf0e6ec4142ad6505b4afe64163e \
--hash=sha256:5bc08e75ed11693ecb648b7a0a4ed80da6d10845e44be0c98c03f2f880b68ff4 \
--hash=sha256:6831a78bbd3c40f909b3e5233f87341f12d0b34a58f14115c9e94b4cdaf726d3 \
--hash=sha256:6a1e03db964e9d32f112bae36f0cc1dcd1988d096cfd75d6a588a3c3def9ab2b \
--hash=sha256:6daeb8382d0df526372abd9cb795c992e18eed25ef2c43afe518c73f8cccb721 \
--hash=sha256:6e7cde3a2221aa89247944cafb1b26616380e30c63e37ed19ff0bba5e968688d \
--hash=sha256:707c8f44931a4facd4149b52b75b80544a8d824162602b8cd2fe788207307f9a \
--hash=sha256:7286c353ee6475613d8beff83167374006c6b3e3f0e6491bfe8ca610eb1dec0f \
--hash=sha256:79cb400c360c7c210097b147c16a9e4c14688a6402445ac848f296ade6283bbc \
--hash=sha256:7cee4c6917857fd6121ed84f56d1dc78eb1d0e87f845ab5a568aba73e78adf83 \
--hash=sha256:80bd73ea335203b125cf1d8e50fef06be709619eb6ab9e7b891ea34b5baa2287 \
--hash=sha256:895184dfef8708e15f7516bd930bda7e50ead069280d2ce09ba11781b630a434 \
--hash=sha256:8fddde2368e777ea2a4891a3fb4341e910a056be0bb15303bf1b92f073b80c02 \
--hash=sha256:95d0b2cf8791ab5fb9e3aa3d9a79a0d5d51f55b6357eecf532a120ba3b5524db \
--hash=sha256:9661268415f450c95f72f0ac1217cc6f10256f860eed85c2ae32e75b60278ad8 \
--hash=sha256:97b850f73f8abbffb66ccbab6e55a195a0eb655e5dc74624d15cff4bfb35bd74 \
--hash=sha256:9ea54f7300553af0a2a7235e9b85f4204e1fc21848f917a3213b0e0818de9a24 \
--hash=sha256:9ebc11c54c6ecdd07bb4efbfa1554538982f5432dfb8456958b6d46b9f834bb7 \
--hash=sha256:a17d8fac6df9835d8e2b4c5523666e7051d0897a93756518a1fe101c7f47f2f0 \
--hash=sha256:ae92bebca3b1e6bd203494e5ef919a60fb6dfe4d9a47ed2453211d3bd451b9f5 \
--hash=sha256:b68094b165a9e930aedef90725a8fcfafe9ef95370cbb54abc0464062dbf808f \
--hash=sha256:b75b00083e7fe6621ce13cfce9d4469c4774e55e8e9d38c305b37f13cf1e874c \
--hash=sha256:bcd18441a49499bf5528deaa9dee1f5c01ca491fc2791b13604e8f972877f812 \
--hash=sha256:bd90c221ed4e60ac9d476db967f436cfcecbd4ef744537c0f2d5291439848768 \
--hash=sha256:c29d03e0adf3cc1a8c3ec62d176824972ae29b67a66cbb18daff3062acc6faa8 \
--hash=sha256:c3330415cd387d2b88600e8e26b510d0370db9b7eaf984354a43e19c40df2e2b \
--hash=sha256:c7db3db284a0edaebe87f8f6642c2b2c27ed85c3e70064b84d1c9e4ec06d5d84 \
--hash=sha256:ce119fc4ce0d64124d37f66a6f2a584fddc3c5001755f8a49f1ca0a177ef9796 \
--hash=sha256:dbcdf987f3aceef9763b6d7b1fd3e4ee210ddd26cac421d78b3c206d07b2700b \
--hash=sha256:e54ef33ea80d464c3dcfe881eb00ad5921b60f8115ea1a30d781653edc2fd6a2 \
--hash=sha256:e60ed6ef0a35c6b76b7640fe452d0e47acc832ccbb8475de549a5cc5f90c2c06 \
--hash=sha256:fb1b30f31a36c7f3fee848391ff77eebdd3af5750bf95fbf9b8b5323edfdb4ec \
--hash=sha256:fbb034f565ecbe6c530dff948239377ba859420d146d5f62f0271407ffb8c580
sqlalchemy==2.0.35 \
--hash=sha256:e11d7ea4d24f0a262bccf9a7cd6284c976c5369dac21db237cff59586045ab9f
# via
# -c requirements/main.txt
# -r requirements/dev.in
Expand Down Expand Up @@ -1795,9 +1747,9 @@ wsproto==1.2.0 \
# via
# selenium-wire
# trio-websocket
zipp==3.20.1 \
--hash=sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064 \
--hash=sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b
zipp==3.20.2 \
--hash=sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350 \
--hash=sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29
# via
# -c requirements/main.txt
# importlib-metadata
Expand Down
Loading

0 comments on commit 5f73719

Please sign in to comment.