Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/image-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 10 additions & 9 deletions litellm/proxy/prisma_migration.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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


Expand Down
26 changes: 12 additions & 14 deletions tests/test_litellm/proxy/test_prisma_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,22 @@ def test_main_disables_migration_check_when_explicitly_false(

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
) -> None:
mock_subprocess_run.return_value = MagicMock(returncode=7, stdout="", stderr="")

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
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, {"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")
Expand Down
Loading