Some checks failed
Use-case regression gate / usecase-regressions (push) Failing after 7m5s
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a17f16-d99c-7ea3-95f0-8212897fba6b
168 lines
5.1 KiB
Python
168 lines
5.1 KiB
Python
"""Rehearse migration 0003 on disposable internal-network PostgreSQL.
|
|
|
|
Uses cached images and read-only source/test dependencies; no published ports,
|
|
production credentials, live data, or production migration. Requires Docker,
|
|
postgres:16, the deployed dd83c90 app image and the locked local dev environment.
|
|
"""
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import sysconfig
|
|
import tempfile
|
|
import uuid
|
|
from pathlib import Path
|
|
|
|
name = "csoc-migration-" + uuid.uuid4().hex[:10]
|
|
root = Path(__file__).resolve().parents[1]
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument(
|
|
"--app-image",
|
|
default="forgejo.coulomb.social/coulomb/coulomb-social:dd83c90",
|
|
)
|
|
parser.add_argument(
|
|
"--packaged-source",
|
|
action="store_true",
|
|
help="Use image source/dependencies, injecting only test tools",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
|
|
def run(*args):
|
|
return subprocess.run(args, capture_output=True, text=True, check=True).stdout.strip()
|
|
|
|
|
|
app = run(
|
|
"docker",
|
|
"image",
|
|
"inspect",
|
|
args.app_image,
|
|
"--format",
|
|
"{{.Id}}",
|
|
)
|
|
pg = run("docker", "image", "inspect", "postgres:16", "--format", "{{.Id}}")
|
|
code = """import time, json, django
|
|
django.setup()
|
|
from django.db import connection
|
|
for i in range(30):
|
|
try:
|
|
connection.ensure_connection()
|
|
break
|
|
except Exception:
|
|
time.sleep(1)
|
|
else:
|
|
raise RuntimeError('isolated_postgres_not_ready')
|
|
from django.core.management import call_command
|
|
call_command('migrate', verbosity=0)
|
|
from tests import test_storage_migration_lifecycle as lifecycle
|
|
assert connection.vendor == 'postgresql'
|
|
lifecycle.test_forward_refusal_rollback_and_reapply_preserve_rows()
|
|
print(json.dumps({
|
|
'status': 'passed', 'database_vendor': connection.vendor,
|
|
'server_version': connection.pg_version, 'forward_collision_refusal': True,
|
|
'refusal_preserves_rows_and_grants': True,
|
|
'rollback_preserves_rows_grants_and_tenant_constraint': True,
|
|
'reapply_enforces_global_constraint': True, 'production_access': False,
|
|
}))
|
|
"""
|
|
tool_directory = None
|
|
source_mounts = ["-v", str(root / "coulomb_social") + ":/app/coulomb_social:ro"]
|
|
dependency_directory = sysconfig.get_path("purelib")
|
|
test_tools = ["pytest", "_pytest", "pluggy", "iniconfig", "pygments", "py.py"]
|
|
if args.packaged_source:
|
|
source_mounts = []
|
|
tool_directory = Path(tempfile.mkdtemp(prefix="csoc-migration-test-tools-"))
|
|
for package in test_tools:
|
|
source = Path(dependency_directory) / package
|
|
if source.is_dir():
|
|
shutil.copytree(source, tool_directory / package)
|
|
else:
|
|
shutil.copyfile(source, tool_directory / package)
|
|
# Only public test-tool sources are copied. The image runs as UID10001.
|
|
tool_directory.chmod(0o755)
|
|
dependency_directory = str(tool_directory)
|
|
code = code.replace(
|
|
"django.setup()",
|
|
"assert django.__file__.startswith('/app/.venv/'), 'packaged_django_required'\n"
|
|
"django.setup()",
|
|
)
|
|
try:
|
|
run("docker", "network", "create", "--internal", name)
|
|
run(
|
|
"docker",
|
|
"run",
|
|
"-d",
|
|
"--rm",
|
|
"--name",
|
|
name,
|
|
"--network",
|
|
name,
|
|
"--network-alias",
|
|
"storage-db",
|
|
"-e",
|
|
"POSTGRES_HOST_AUTH_METHOD=trust",
|
|
"-e",
|
|
"POSTGRES_DB=storage_rehearsal",
|
|
pg,
|
|
)
|
|
result = run(
|
|
"docker",
|
|
"run",
|
|
"--rm",
|
|
"--network",
|
|
name,
|
|
"--read-only",
|
|
"--tmpfs",
|
|
"/tmp",
|
|
"--entrypoint",
|
|
"/app/.venv/bin/python",
|
|
"-e",
|
|
"DJANGO_SETTINGS_MODULE=coulomb_social.settings.base",
|
|
"-e",
|
|
"DATABASE_URL=postgres://postgres@storage-db/storage_rehearsal",
|
|
"-e",
|
|
"PYTHONDONTWRITEBYTECODE=1",
|
|
*source_mounts,
|
|
"-v",
|
|
str(root / "tests") + ":/app/tests:ro",
|
|
"-v",
|
|
dependency_directory + ":/test-deps:ro",
|
|
"-e",
|
|
"PYTHONPATH=/test-deps",
|
|
app,
|
|
"-c",
|
|
code,
|
|
)
|
|
print(
|
|
json.dumps(
|
|
{
|
|
"app_dependency_image": app,
|
|
"packaged_source_and_django": args.packaged_source,
|
|
"injected_test_tools": test_tools
|
|
if args.packaged_source
|
|
else "locked dev environment",
|
|
"postgres_image": pg,
|
|
"proof": json.loads(result.splitlines()[-1]),
|
|
"source_sha256": {
|
|
str(p.relative_to(root)): hashlib.sha256(p.read_bytes()).hexdigest()
|
|
for p in [
|
|
root / "tests/test_storage_migration_lifecycle.py",
|
|
root
|
|
/ "coulomb_social/apps/spaces/migrations/0003_space_slug_global_uniq.py",
|
|
root / "uv.lock",
|
|
]
|
|
},
|
|
},
|
|
indent=2,
|
|
)
|
|
)
|
|
except subprocess.CalledProcessError as e:
|
|
print(e.stdout, e.stderr)
|
|
raise
|
|
finally:
|
|
subprocess.run(["docker", "rm", "-fv", name], capture_output=True)
|
|
subprocess.run(["docker", "network", "rm", name], capture_output=True)
|
|
if tool_directory is not None:
|
|
shutil.rmtree(tool_directory)
|