mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
fix(proxy): keep a failed prisma generate from failing the migration entrypoint (#37947)
The standalone migration entrypoint re-runs `prisma generate` after the migration completes. That refresh writes into the installed prisma package in site-packages, which an arbitrary non-root uid cannot do, and which no uid can do under a read-only root filesystem. Both are supported configurations of the migrations Job: helm/litellm-helm/tests/migrations-job_tests.yaml asserts runAsNonRoot, runAsUser and readOnlyRootFilesystem all render. The write has always failed there, but the failure used to be swallowed. Making migration failures fatal turned it into a hard exit 1, so a Job that applied every migration correctly now reports Failed and blocks the rollout it was supposed to gate. The refresh is redundant in the shipped images: every Dockerfile generates the client at build time from the same baked schema, copies it into the runtime stage, and asserts it resolves there. It stays load-bearing only for a source checkout, where CircleCI runs the entrypoint under `set +e` and ignores the exit code anyway. So the call stays and only its exit code stops propagating; migration failures are still fatal. image-scan never ran on the change that introduced this, because its path filter did not list the entrypoint it exercises. Add prisma_migration.py and entrypoint.sh so the non-root offline migration test gates them from now on.
This commit is contained in:
parent
abf99e37d6
commit
deab3676e8
3 changed files with 24 additions and 23 deletions
2
.github/workflows/image-scan.yml
vendored
2
.github/workflows/image-scan.yml
vendored
|
|
@ -17,6 +17,8 @@ on:
|
|||
- backend/Dockerfile
|
||||
- backend/main.py
|
||||
- docker/component_entrypoint.sh
|
||||
- docker/entrypoint.sh
|
||||
- litellm/proxy/prisma_migration.py
|
||||
- litellm-proxy-extras/**
|
||||
- tests/proxy_migration_tests/**
|
||||
- uv.lock
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
"""Standalone entrypoint for applying database migrations and generating the Prisma client.
|
||||
|
||||
The entrypoint enforces migration failures by default. Set
|
||||
ENFORCE_PRISMA_MIGRATION_CHECK=false to preserve log-only behavior for migration and
|
||||
Prisma generate failures.
|
||||
Migration failures fail the entrypoint by default; set ENFORCE_PRISMA_MIGRATION_CHECK=false
|
||||
for log-only behavior. A failed 'prisma generate' is always log-only: every shipped image
|
||||
bakes the client at build time, and refreshing it writes into site-packages, which an
|
||||
arbitrary non-root uid or a read-only root filesystem cannot do.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
|
@ -30,13 +31,13 @@ def main() -> int:
|
|||
verbose_proxy_logger.info("Running 'prisma generate'...")
|
||||
result: Final = subprocess.run(("prisma", "generate"), capture_output=True, text=True)
|
||||
verbose_proxy_logger.info("'prisma generate' stdout: %s", result.stdout)
|
||||
exit_code: Final = result.returncode
|
||||
|
||||
if exit_code != 0:
|
||||
verbose_proxy_logger.info("'prisma generate' failed with exit code %s.", exit_code)
|
||||
verbose_proxy_logger.error("'prisma generate' stderr: %s", result.stderr)
|
||||
if enforce_prisma_migration_check:
|
||||
return exit_code
|
||||
if result.returncode != 0:
|
||||
verbose_proxy_logger.warning(
|
||||
"'prisma generate' exited %s; continuing with the client baked at image build time. stderr: %s",
|
||||
result.returncode,
|
||||
result.stderr,
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -34,24 +34,22 @@ class TestPrismaMigration:
|
|||
|
||||
mock_run_server.assert_called_once_with(("--skip_server_startup",), standalone_mode=False)
|
||||
|
||||
@pytest.mark.parametrize("env", [{}, {"ENFORCE_PRISMA_MIGRATION_CHECK": "false"}])
|
||||
@patch("litellm.proxy.prisma_migration.subprocess.run")
|
||||
@patch("litellm.proxy.prisma_migration.run_server")
|
||||
def test_main_returns_prisma_generate_exit_code_when_enforced(
|
||||
self, mock_run_server: MagicMock, mock_subprocess_run: MagicMock
|
||||
def test_main_exits_zero_when_only_prisma_generate_fails(
|
||||
self,
|
||||
mock_run_server: MagicMock,
|
||||
mock_subprocess_run: MagicMock,
|
||||
env: dict[str, str],
|
||||
) -> None:
|
||||
mock_subprocess_run.return_value = MagicMock(returncode=7, stdout="", stderr="")
|
||||
mock_subprocess_run.return_value = MagicMock(
|
||||
returncode=1,
|
||||
stdout="",
|
||||
stderr="PermissionError: [Errno 13] Permission denied: '/app/.venv/lib/python3.13/site-packages/prisma/schema.prisma'",
|
||||
)
|
||||
|
||||
with patch.dict(os.environ, {}, clear=True):
|
||||
assert prisma_migration.main() == 7
|
||||
|
||||
@patch("litellm.proxy.prisma_migration.subprocess.run")
|
||||
@patch("litellm.proxy.prisma_migration.run_server")
|
||||
def test_main_ignores_prisma_generate_exit_code_when_disabled(
|
||||
self, mock_run_server: MagicMock, mock_subprocess_run: MagicMock
|
||||
) -> None:
|
||||
mock_subprocess_run.return_value = MagicMock(returncode=7, stdout="", stderr="")
|
||||
|
||||
with patch.dict(os.environ, {"ENFORCE_PRISMA_MIGRATION_CHECK": "false"}, clear=True):
|
||||
with patch.dict(os.environ, env, clear=True):
|
||||
assert prisma_migration.main() == 0
|
||||
|
||||
@patch("litellm.proxy.prisma_migration.subprocess.run")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue