Compare commits

...

8 Commits

Author SHA1 Message Date
Jens Langhammer
9b04f13561 fix some issues?
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-09-24 17:50:36 +02:00
Jens Langhammer
f14f362180 kinda fix
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-09-24 16:14:33 +02:00
Jens Langhammer
5ae3186030 add mirror based on https://docs.djangoproject.com/en/5.2/topics/testing/advanced/#testing-primary-replica-configurations
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-09-24 16:06:58 +02:00
Jens Langhammer
3a9d81f459 improve
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-09-24 15:26:07 +02:00
Jens Langhammer
c0ec5cf63c fix typing
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-09-24 14:48:08 +02:00
Jens Langhammer
bbfcc9c6ab generate config to use replicas
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-09-24 14:36:43 +02:00
Jens Langhammer
6e802356ce fix hostname
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-09-24 14:36:35 +02:00
Jens Langhammer
6e362212ec ci: add tests with postgres replicas
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2025-09-24 14:31:56 +02:00
9 changed files with 104 additions and 21 deletions

View File

@@ -8,6 +8,9 @@ inputs:
postgresql_version:
description: "Optional postgresql image tag"
default: "16"
profiles:
description: "Extra profiles of supporting services to start"
default: ""
runs:
using: "composite"
@@ -55,21 +58,13 @@ runs:
shell: bash
run: |
export PSQL_TAG=${{ inputs.postgresql_version }}
export COMPOSE_PROFILES=${{ inputs.profiles }}
docker compose -f .github/actions/setup/docker-compose.yml up -d
cd web && npm ci
- name: Generate config
if: ${{ contains(inputs.dependencies, 'python') }}
shell: uv run python {0}
shell: bash
env:
PROFILES: ${{ inputs.profiles }}
run: |
from authentik.lib.generators import generate_id
from yaml import safe_dump
with open("local.env.yml", "w") as _config:
safe_dump(
{
"log_level": "debug",
"secret_key": generate_id(),
},
_config,
default_flow_style=False,
)
uv run python3 ${{ github.action_path }}/ci_config.py

18
.github/actions/setup/ci_config.py vendored Normal file
View File

@@ -0,0 +1,18 @@
from os import getenv
from typing import Any
from yaml import safe_dump
from authentik.lib.generators import generate_id
config: dict[str, Any] = {
"log_level": "debug",
"secret_key": generate_id(),
}
profiles = getenv("PROFILES")
if profiles and "postgres_replica" in profiles:
config["postgresql"] = {"read_replicas": {"0": {"host": "localhost", "port": 5433}}}
with open("local.env.yml", "w") as _config:
safe_dump(config, _config, default_flow_style=False)

View File

@@ -1,8 +1,17 @@
services:
postgresql:
redis:
image: docker.io/library/redis:7
ports:
- 6379:6379
restart: always
postgres:
image: docker.io/library/postgres:${PSQL_TAG:-16}
volumes:
- db-data:/var/lib/postgresql/data
- ./primary/00-replication.sql:/docker-entrypoint-initdb.d/00-replication.sql
- ./primary/01-replication-hba.sh:/docker-entrypoint-initdb.d/01-replication-hba.sh
command: postgres -c 'wal_level=replica' -c 'max_wal_senders=10' -c 'max_replication_slots=10' -c 'listen_addresses=*'
environment:
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
@@ -10,12 +19,34 @@ services:
ports:
- 5432:5432
restart: always
redis:
image: docker.io/library/redis:7
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
interval: 5s
timeout: 5s
retries: 5
postgres_replica:
profiles:
- postgres_replica
image: docker.io/library/postgres:${PSQL_TAG:-16}
environment:
POSTGRES_USER: authentik
POSTGRES_PASSWORD: "EK-5jnKfjrGRm<77"
POSTGRES_DB: authentik
ports:
- 6379:6379
restart: always
- "5433:5432"
volumes:
- db-data-replica:/var/lib/postgresql/data
- ./replica:/replica
command: /replica/start.sh
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"]
interval: 5s
timeout: 5s
retries: 5
volumes:
db-data:
driver: local
db-data-replica:
driver: local

View File

@@ -0,0 +1,9 @@
-- Create replication role if it doesn't exist
DO $$ BEGIN
IF NOT EXISTS (SELECT FROM pg_catalog.pg_roles WHERE rolname = 'replica') THEN
CREATE ROLE replica WITH REPLICATION LOGIN PASSWORD 'EK-5jnKfjrGRm<77';
END IF;
END $$;
-- Create replication slot if it doesn't exist
SELECT pg_create_physical_replication_slot('replica_slot', true);

View File

@@ -0,0 +1,3 @@
#!/bin/bash
set -euxo pipefail
echo "host replication all all scram-sha-256" >> /var/lib/postgresql/data/pg_hba.conf

9
.github/actions/setup/replica/start.sh vendored Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
set -euxo pipefail
echo 'Waiting for primary to be ready...'
while ! pg_isready -h postgres -p 5432 -U replica; do sleep 1; done;
echo 'Primary is ready, starting replica...'
rm -rf /var/lib/postgresql/data/* 2>/dev/null || true
PGPASSWORD=${POSTGRES_PASSWORD} pg_basebackup -h postgres -U replica -D /var/lib/postgresql/data -Fp -Xs -R -P
echo 'Replication setup complete, starting PostgreSQL...'
docker-entrypoint.sh postgres

View File

@@ -67,7 +67,6 @@ jobs:
fail-fast: false
matrix:
psql:
- 15-alpine
- 16-alpine
- 17-alpine
run_id: [1, 2, 3, 4, 5]
@@ -114,7 +113,7 @@ jobs:
run: |
uv run make ci-test
test-unittest:
name: test-unittest - PostgreSQL ${{ matrix.psql }} - Run ${{ matrix.run_id }}/5
name: test-unittest - PostgreSQL ${{ matrix.psql }} (${{ matrix.profiles }}) - Run ${{ matrix.run_id }}/5
runs-on: ubuntu-latest
timeout-minutes: 20
needs: test-make-seed
@@ -122,9 +121,11 @@ jobs:
fail-fast: false
matrix:
psql:
- 15-alpine
- 16-alpine
- 17-alpine
profiles:
- ""
- postgres_replica
run_id: [1, 2, 3, 4, 5]
steps:
- uses: actions/checkout@v5
@@ -132,6 +133,7 @@ jobs:
uses: ./.github/actions/setup
with:
postgresql_version: ${{ matrix.psql }}
profiles: ${{ matrix.profiles }}
- name: run unittest
env:
CI_TEST_SEED: ${{ needs.test-make-seed.outputs.seed }}

View File

@@ -444,6 +444,10 @@ def django_db_config(config: ConfigLoader | None = None) -> dict:
f"postgresql.read_replicas.{replica}.conn_options", default={}
)
_database["OPTIONS"].update(replica_conn_options)
_database["TEST"] = {
"MIRROR": "default",
"NAME": config.get("postgresql.test.name"),
}
db[f"replica_{replica}"] = _database
return db

View File

@@ -8,6 +8,7 @@ from unittest.mock import patch
import pytest
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase as DjangoTestCase
from django.test.runner import DiscoverRunner
from structlog.stdlib import get_logger
@@ -20,6 +21,8 @@ from authentik.tasks.test import use_test_broker
# globally set maxDiff to none to show full assert error
TestCase.maxDiff = None
# allow testing with read-replicas
DjangoTestCase.databases = "__all__"
def get_docker_tag() -> str:
@@ -63,6 +66,15 @@ class PytestTestRunner(DiscoverRunner): # pragma: no cover
settings.TEST = True
settings.DRAMATIQ["test"] = True
# Set any other test databases's name to their test name early
# django does this itself, however only _after_ migrating the default alias
# which triggers some reads that might go to the read replica, which
# would be routed to the wrong database
for alias, db in settings.DATABASES.items():
if alias == "default":
continue
db["NAME"] = db["TEST"]["NAME"]
# Test-specific configuration
test_config = {
"events.context_processors.geoip": "tests/GeoLite2-City-Test.mmdb",