WIP
This commit is contained in:
parent
e70a0584c9
commit
a7a5b5c8ea
43 changed files with 5531 additions and 9 deletions
219
tests/test_runtime_plans.py
Normal file
219
tests/test_runtime_plans.py
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from control_plane.runtime_plans import DjangoApplicationLaunchConfig
|
||||
from control_plane.runtime_plans import DjangoContainerImageBuildConfig
|
||||
from control_plane.runtime_plans import DjangoContainerRuntimeConfig
|
||||
from control_plane.runtime_plans import PostgresContainerConfig
|
||||
from control_plane.runtime_plans import RedisContainerConfig
|
||||
from control_plane.runtime_plans import build_django_container_image_command
|
||||
from control_plane.runtime_plans import build_django_container_run_command
|
||||
from control_plane.runtime_plans import build_django_migrate_command
|
||||
from control_plane.runtime_plans import build_django_server_command
|
||||
from control_plane.runtime_plans import build_postgres_container_command
|
||||
from control_plane.runtime_plans import build_redis_container_command
|
||||
|
||||
|
||||
def test_build_postgres_container_command_uses_hardening_flags() -> None:
|
||||
"""PostgreSQL container plan should include core isolation and auth settings."""
|
||||
command = build_postgres_container_command(
|
||||
PostgresContainerConfig(
|
||||
container_name="postgres-tenant-site",
|
||||
network_name="tenant-net",
|
||||
hostname="postgres.internal",
|
||||
username="site",
|
||||
database_name="site",
|
||||
data_directory=Path("/var/lib/tussilago/postgres/site"),
|
||||
password_file=Path("/run/tussilago/secrets/postgres-password"),
|
||||
),
|
||||
)
|
||||
|
||||
assert "--cap-drop=all" in command
|
||||
assert "--cap-add=CHOWN" in command
|
||||
assert "--cap-add=FOWNER" in command
|
||||
assert "--cap-add=SETUID" in command
|
||||
assert "--cap-add=SETGID" in command
|
||||
assert "--cap-add=DAC_OVERRIDE" in command
|
||||
assert "--security-opt=no-new-privileges" in command
|
||||
assert "POSTGRES_PASSWORD_FILE=/run/secrets/postgres-password" in command
|
||||
assert "password_encryption=scram-sha-256" in command
|
||||
assert command[-1] == "password_encryption=scram-sha-256"
|
||||
|
||||
|
||||
def test_build_redis_container_command_requires_password() -> None:
|
||||
"""Redis container plan should enforce password auth and readonly base image."""
|
||||
command = build_redis_container_command(
|
||||
RedisContainerConfig(
|
||||
container_name="redis-tenant-site",
|
||||
network_name="tenant-net",
|
||||
hostname="redis.internal",
|
||||
data_directory=Path("/var/lib/tussilago/redis/site"),
|
||||
password_file=Path("/run/tussilago/secrets/redis-password"),
|
||||
),
|
||||
)
|
||||
|
||||
assert "--read-only" in command
|
||||
assert "/run/secrets/redis-password" in command[-1]
|
||||
assert command[-1].startswith("redis_password=$(cat /run/secrets/redis-password)")
|
||||
|
||||
|
||||
def test_build_django_server_command_supports_project_reuse() -> None:
|
||||
"""Gunicorn command should optionally reuse the repo-managed uv project."""
|
||||
command = build_django_server_command(
|
||||
DjangoApplicationLaunchConfig(
|
||||
wsgi_module="tenant_site.wsgi:application",
|
||||
bind_host="127.0.0.1",
|
||||
port=9000,
|
||||
workers=1,
|
||||
uv_project_path=Path("/workspace/Tussilago"),
|
||||
),
|
||||
)
|
||||
|
||||
assert command == (
|
||||
"uv",
|
||||
"run",
|
||||
"--project",
|
||||
"/workspace/Tussilago",
|
||||
"gunicorn",
|
||||
"--bind",
|
||||
"127.0.0.1:9000",
|
||||
"--workers",
|
||||
"1",
|
||||
"--access-logfile",
|
||||
"-",
|
||||
"--error-logfile",
|
||||
"-",
|
||||
"--capture-output",
|
||||
"--graceful-timeout",
|
||||
"30",
|
||||
"--timeout",
|
||||
"60",
|
||||
"tenant_site.wsgi:application",
|
||||
)
|
||||
|
||||
|
||||
def test_build_django_migrate_command_supports_repo_reuse() -> None:
|
||||
"""Migration command should optionally reuse repo-managed uv dependencies."""
|
||||
assert build_django_migrate_command(Path("/workspace/Tussilago")) == (
|
||||
"uv",
|
||||
"run",
|
||||
"--project",
|
||||
"/workspace/Tussilago",
|
||||
"python",
|
||||
"manage.py",
|
||||
"migrate",
|
||||
"--noinput",
|
||||
)
|
||||
|
||||
|
||||
def test_build_django_migrate_command_supports_explicit_python_executable() -> None:
|
||||
"""Migration command should also support container-local Python execution."""
|
||||
assert build_django_migrate_command(python_executable=Path("/usr/local/bin/python")) == (
|
||||
"/usr/local/bin/python",
|
||||
"manage.py",
|
||||
"migrate",
|
||||
"--noinput",
|
||||
)
|
||||
|
||||
|
||||
def test_build_django_container_image_command_targets_checked_in_containerfile() -> None:
|
||||
"""Image build plan should reference an explicit Containerfile and context."""
|
||||
command = build_django_container_image_command(
|
||||
DjangoContainerImageBuildConfig(
|
||||
image_reference="localhost/tussilago-test-django:latest",
|
||||
containerfile_path=Path("/workspace/Tussilago/control_plane/container_assets/test_django/Containerfile"),
|
||||
context_directory=Path("/workspace/Tussilago/control_plane/container_assets/test_django"),
|
||||
),
|
||||
)
|
||||
|
||||
assert command == (
|
||||
"podman",
|
||||
"build",
|
||||
"--pull=missing",
|
||||
"--tag",
|
||||
"localhost/tussilago-test-django:latest",
|
||||
"--file",
|
||||
"/workspace/Tussilago/control_plane/container_assets/test_django/Containerfile",
|
||||
"/workspace/Tussilago/control_plane/container_assets/test_django",
|
||||
)
|
||||
|
||||
|
||||
def test_build_django_container_run_command_uses_publish_and_secret_mounts() -> None:
|
||||
"""Django runtime plan should publish localhost only and mount generated assets read-only."""
|
||||
command = build_django_container_run_command(
|
||||
DjangoContainerRuntimeConfig(
|
||||
container_name="django-tenant-site",
|
||||
network_name="tenant-net",
|
||||
hostname="django.internal",
|
||||
image_reference="localhost/tussilago-test-django:latest",
|
||||
application_directory=Path("/var/lib/tussilago/test-app"),
|
||||
host_port=19000,
|
||||
environment=(
|
||||
("DJANGO_SECRET_KEY", "secret"),
|
||||
("DJANGO_SETTINGS_MODULE", "tenant_site.settings"),
|
||||
),
|
||||
secret_mounts=((Path("/var/lib/tussilago/postgres-secret"), "/run/postgres-secret"),),
|
||||
labels=(("tussilago.deployment-id", "deployment-1"),),
|
||||
),
|
||||
command=("/usr/local/bin/python", "-m", "gunicorn"),
|
||||
detach=True,
|
||||
)
|
||||
|
||||
assert "--read-only" in command
|
||||
assert "--publish" in command
|
||||
assert "127.0.0.1:19000:8000" in command
|
||||
assert "/var/lib/tussilago/test-app:/srv/test-app:Z,ro" in command
|
||||
assert "/var/lib/tussilago/postgres-secret:/run/postgres-secret:Z,ro" in command
|
||||
assert "DJANGO_SETTINGS_MODULE=tenant_site.settings" in command
|
||||
assert "tussilago.deployment-id=deployment-1" in command
|
||||
|
||||
|
||||
def test_build_django_container_run_command_supports_pod_membership() -> None:
|
||||
"""Django runtime plan should join a Podman pod instead of publishing ports when requested."""
|
||||
command = build_django_container_run_command(
|
||||
DjangoContainerRuntimeConfig(
|
||||
container_name="django-tenant-site",
|
||||
network_name="ignored-net",
|
||||
hostname="django.internal",
|
||||
image_reference="localhost/tussilago-test-django:latest",
|
||||
application_directory=Path("/var/lib/tussilago/test-app"),
|
||||
pod_name="pod-tenant-site",
|
||||
host_port=19000,
|
||||
),
|
||||
command=("/usr/local/bin/python", "-m", "gunicorn"),
|
||||
detach=True,
|
||||
)
|
||||
|
||||
assert "--pod" in command
|
||||
assert "pod-tenant-site" in command
|
||||
assert "--publish" not in command
|
||||
|
||||
|
||||
def test_build_django_server_command_supports_explicit_python_executable() -> None:
|
||||
"""Local smoke flows may launch Gunicorn via an explicit Python executable."""
|
||||
command = build_django_server_command(
|
||||
DjangoApplicationLaunchConfig(
|
||||
wsgi_module="tenant_site.wsgi:application",
|
||||
bind_host="127.0.0.1",
|
||||
port=9000,
|
||||
workers=1,
|
||||
python_executable=Path("/venv/bin/python"),
|
||||
),
|
||||
)
|
||||
|
||||
assert command[:3] == ("/venv/bin/python", "-m", "gunicorn")
|
||||
|
||||
|
||||
def test_build_django_server_command_rejects_conflicting_python_modes() -> None:
|
||||
"""Server command builder should reject mixed uv and direct-python execution modes."""
|
||||
with pytest.raises(ValueError, match="mutually exclusive"):
|
||||
build_django_server_command(
|
||||
DjangoApplicationLaunchConfig(
|
||||
wsgi_module="tenant_site.wsgi:application",
|
||||
python_executable=Path("/venv/bin/python"),
|
||||
uv_project_path=Path("/workspace/Tussilago"),
|
||||
),
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue