diff --git a/.github/workflows/image-scan.yml b/.github/workflows/image-scan.yml index 8faf3ef62297..d798df4c3a49 100644 --- a/.github/workflows/image-scan.yml +++ b/.github/workflows/image-scan.yml @@ -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 diff --git a/litellm/proxy/prisma_migration.py b/litellm/proxy/prisma_migration.py index 373c38119497..1b95d24c011c 100644 --- a/litellm/proxy/prisma_migration.py +++ b/litellm/proxy/prisma_migration.py @@ -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 diff --git a/tests/test_litellm/proxy/test_prisma_migration.py b/tests/test_litellm/proxy/test_prisma_migration.py index 01b768ea8dc9..729adcfb9e0b 100644 --- a/tests/test_litellm/proxy/test_prisma_migration.py +++ b/tests/test_litellm/proxy/test_prisma_migration.py @@ -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")