mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
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.
66 lines
2.6 KiB
Python
66 lines
2.6 KiB
Python
import os
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from litellm.proxy import prisma_migration
|
|
|
|
|
|
class TestPrismaMigration:
|
|
@patch("litellm.proxy.prisma_migration.subprocess.run")
|
|
@patch("litellm.proxy.prisma_migration.run_server")
|
|
def test_main_enforces_migration_check_by_default(
|
|
self, mock_run_server: MagicMock, mock_subprocess_run: MagicMock
|
|
) -> None:
|
|
mock_subprocess_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
|
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
assert prisma_migration.main() == 0
|
|
|
|
mock_run_server.assert_called_once_with(
|
|
("--skip_server_startup", "--enforce_prisma_migration_check"),
|
|
standalone_mode=False,
|
|
)
|
|
|
|
@patch("litellm.proxy.prisma_migration.subprocess.run")
|
|
@patch("litellm.proxy.prisma_migration.run_server")
|
|
def test_main_disables_migration_check_when_explicitly_false(
|
|
self, mock_run_server: MagicMock, mock_subprocess_run: MagicMock
|
|
) -> None:
|
|
mock_subprocess_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
|
|
|
with patch.dict(os.environ, {"ENFORCE_PRISMA_MIGRATION_CHECK": "false"}, clear=True):
|
|
assert prisma_migration.main() == 0
|
|
|
|
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_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=1,
|
|
stdout="",
|
|
stderr="PermissionError: [Errno 13] Permission denied: '/app/.venv/lib/python3.13/site-packages/prisma/schema.prisma'",
|
|
)
|
|
|
|
with patch.dict(os.environ, env, clear=True):
|
|
assert prisma_migration.main() == 0
|
|
|
|
@patch("litellm.proxy.prisma_migration.subprocess.run")
|
|
@patch("litellm.proxy.prisma_migration.run_server")
|
|
def test_main_propagates_migration_failure(
|
|
self, mock_run_server: MagicMock, mock_subprocess_run: MagicMock
|
|
) -> None:
|
|
mock_run_server.side_effect = SystemExit(1)
|
|
|
|
with patch.dict(os.environ, {}, clear=True):
|
|
with pytest.raises(SystemExit, match="1"):
|
|
prisma_migration.main()
|
|
|
|
mock_subprocess_run.assert_not_called()
|