Merge pull request #24705 from BerriAI/litellm_auto_schema_sync
[Infra] Automated schema.prisma sync and drift detection
This commit is contained in:
commit
fe080a86b2
58
.github/workflows/check-schema-sync.yml
vendored
Normal file
58
.github/workflows/check-schema-sync.yml
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
name: Check Schema Sync
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'schema.prisma'
|
||||
- 'litellm/proxy/schema.prisma'
|
||||
- 'litellm-proxy-extras/litellm_proxy_extras/schema.prisma'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check-sync:
|
||||
name: Verify schema.prisma copies match root
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Reject symlinked schema files
|
||||
run: |
|
||||
for f in schema.prisma litellm/proxy/schema.prisma litellm-proxy-extras/litellm_proxy_extras/schema.prisma; do
|
||||
if [ -L "$f" ]; then
|
||||
echo "::error file=$f::$f is a symlink, which is not allowed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Check all schemas match root
|
||||
run: |
|
||||
EXIT=0
|
||||
|
||||
diff schema.prisma litellm/proxy/schema.prisma || {
|
||||
echo "::error file=litellm/proxy/schema.prisma::litellm/proxy/schema.prisma differs from root schema.prisma"
|
||||
EXIT=1
|
||||
}
|
||||
|
||||
diff schema.prisma litellm-proxy-extras/litellm_proxy_extras/schema.prisma || {
|
||||
echo "::error file=litellm-proxy-extras/litellm_proxy_extras/schema.prisma::litellm-proxy-extras/litellm_proxy_extras/schema.prisma differs from root schema.prisma"
|
||||
EXIT=1
|
||||
}
|
||||
|
||||
if [ "$EXIT" -ne 0 ]; then
|
||||
echo ""
|
||||
echo "Schema files are out of sync."
|
||||
echo "The root schema.prisma is the source of truth."
|
||||
echo ""
|
||||
echo "To fix, run from the repo root:"
|
||||
echo " cp schema.prisma litellm/proxy/schema.prisma"
|
||||
echo " cp schema.prisma litellm-proxy-extras/litellm_proxy_extras/schema.prisma"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "All schema copies are in sync with root."
|
||||
73
.github/workflows/sync-schema.yml
vendored
Normal file
73
.github/workflows/sync-schema.yml
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
name: Sync schema.prisma copies
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'schema.prisma'
|
||||
|
||||
# Scoped to ONLY the permissions needed:
|
||||
# - contents:write to push the sync commit to the PR branch
|
||||
# - pull-requests:read is implicit (needed to check out the PR)
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: Copy root schema to proxy and proxy-extras
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
# Only run on PRs from branches in THIS repo (not forks).
|
||||
# Fork PRs cannot push back to the head branch with GITHUB_TOKEN,
|
||||
# and pull_request events from forks have read-only tokens anyway.
|
||||
# Also reject PRs from branches named after protected branches to
|
||||
# prevent pushing directly to main/master.
|
||||
if: >-
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
&& github.head_ref != 'main'
|
||||
&& github.head_ref != 'master'
|
||||
steps:
|
||||
- name: Checkout PR branch by SHA
|
||||
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
|
||||
with:
|
||||
# Use the merge commit SHA for safety — github.head_ref is an
|
||||
# attacker-controlled string (the branch name) and could contain
|
||||
# unusual characters that cause unexpected git behavior.
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
persist-credentials: true # needed for git push
|
||||
|
||||
- name: Reject symlinked schema files
|
||||
run: |
|
||||
for f in schema.prisma litellm/proxy/schema.prisma litellm-proxy-extras/litellm_proxy_extras/schema.prisma; do
|
||||
if [ -L "$f" ]; then
|
||||
echo "::error file=$f::$f is a symlink, which is not allowed"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Copy root schema to other locations
|
||||
run: |
|
||||
cp schema.prisma litellm/proxy/schema.prisma
|
||||
cp schema.prisma litellm-proxy-extras/litellm_proxy_extras/schema.prisma
|
||||
|
||||
- name: Check for changes
|
||||
id: diff
|
||||
run: |
|
||||
if git diff --quiet -- litellm/proxy/schema.prisma litellm-proxy-extras/litellm_proxy_extras/schema.prisma; then
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Schemas already in sync. Nothing to do."
|
||||
else
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Schema copies need updating."
|
||||
fi
|
||||
|
||||
- name: Commit synced schemas
|
||||
if: steps.diff.outputs.changed == 'true'
|
||||
run: |
|
||||
# Push to the PR's head branch (need the branch name for git push).
|
||||
# We checked out by SHA above for safety, so configure the push target explicitly.
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git checkout -B "$GITHUB_HEAD_REF"
|
||||
git add -- litellm/proxy/schema.prisma litellm-proxy-extras/litellm_proxy_extras/schema.prisma
|
||||
git commit -m "chore: sync schema.prisma copies from root"
|
||||
git push origin "HEAD:$GITHUB_HEAD_REF"
|
||||
@ -1,11 +0,0 @@
|
||||
-- DropIndex
|
||||
DROP INDEX IF EXISTS "LiteLLM_MCPServerTable_approval_status_idx";
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "LiteLLM_MCPServerTable" DROP COLUMN IF EXISTS "approval_status",
|
||||
DROP COLUMN IF EXISTS "review_notes",
|
||||
DROP COLUMN IF EXISTS "reviewed_at",
|
||||
DROP COLUMN IF EXISTS "source_url",
|
||||
DROP COLUMN IF EXISTS "submitted_at",
|
||||
DROP COLUMN IF EXISTS "submitted_by";
|
||||
|
||||
@ -320,11 +320,15 @@ model LiteLLM_MCPServerTable {
|
||||
is_byok Boolean @default(false)
|
||||
byok_description String[] @default([])
|
||||
byok_api_key_help_url String?
|
||||
approval_status String @default("approved")
|
||||
submitted_by String?
|
||||
submitted_at DateTime?
|
||||
reviewed_at DateTime?
|
||||
review_notes String?
|
||||
source_url String?
|
||||
// BYOM submission lifecycle
|
||||
approval_status String? @default("active")
|
||||
submitted_by String?
|
||||
submitted_at DateTime?
|
||||
reviewed_at DateTime?
|
||||
review_notes String?
|
||||
|
||||
@@index([approval_status])
|
||||
}
|
||||
|
||||
// Per-user BYOK credentials for MCP servers
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "litellm-proxy-extras"
|
||||
version = "0.4.60"
|
||||
version = "0.4.61"
|
||||
description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package."
|
||||
authors = ["BerriAI"]
|
||||
readme = "README.md"
|
||||
@ -22,7 +22,7 @@ requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.commitizen]
|
||||
version = "0.4.60"
|
||||
version = "0.4.61"
|
||||
version_files = [
|
||||
"pyproject.toml:version",
|
||||
"../requirements.txt:litellm-proxy-extras==",
|
||||
|
||||
8
poetry.lock
generated
8
poetry.lock
generated
@ -3219,15 +3219,15 @@ files = [
|
||||
|
||||
[[package]]
|
||||
name = "litellm-proxy-extras"
|
||||
version = "0.4.60"
|
||||
version = "0.4.61"
|
||||
description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package."
|
||||
optional = true
|
||||
python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8"
|
||||
groups = ["main"]
|
||||
markers = "extra == \"proxy\""
|
||||
files = [
|
||||
{file = "litellm_proxy_extras-0.4.60-py3-none-any.whl", hash = "sha256:7abcc811f7430e4b24e7a8ba7186219a4845a955ae7a71d8822bd03fd9fc3393"},
|
||||
{file = "litellm_proxy_extras-0.4.60.tar.gz", hash = "sha256:1c122f2a7e0eb58fa4c6d8da9da82ac1fe2869de3510bcfade5c2932af202328"},
|
||||
{file = "litellm_proxy_extras-0.4.61-py3-none-any.whl", hash = "sha256:9bd1e57ef51972cacff52172ef5d70b0ff689f57f3d240877667301ab8f8590e"},
|
||||
{file = "litellm_proxy_extras-0.4.61.tar.gz", hash = "sha256:dce8e39b1547abf90d912ddd0f2a876beadf789d700ef04c165362d78ad56aee"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -8009,4 +8009,4 @@ utils = ["numpydoc"]
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.9,<4.0"
|
||||
content-hash = "b4e3ee072f600fab9810024afdd550407d25733a9b6752476aa61826e33bc08e"
|
||||
content-hash = "8dad0e86d75e574f12c57c9f32614b7b4ea2181e931874046d43a398aef1e998"
|
||||
|
||||
@ -61,7 +61,7 @@ boto3 = { version = "^1.40.76", optional = true }
|
||||
redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.9' and python_version < '3.14'"}
|
||||
mcp = {version = ">=1.25.0,<2.0.0", optional = true, python = ">=3.10"}
|
||||
a2a-sdk = {version = "^0.3.22", optional = true, python = ">=3.10"}
|
||||
litellm-proxy-extras = {version = "^0.4.60", optional = true}
|
||||
litellm-proxy-extras = {version = "^0.4.61", optional = true}
|
||||
rich = {version = "^13.7.1", optional = true}
|
||||
litellm-enterprise = {version = "0.1.35", optional = true}
|
||||
diskcache = {version = "^5.6.1", optional = true}
|
||||
|
||||
@ -57,7 +57,7 @@ grpcio>=1.75.0; python_version >= "3.14"
|
||||
sentry_sdk==2.21.0 # for sentry error handling
|
||||
detect-secrets==1.5.0 # Enterprise - secret detection / masking in LLM requests
|
||||
tzdata==2025.1 # IANA time zone database
|
||||
litellm-proxy-extras==0.4.60 # for proxy extras - e.g. prisma migrations
|
||||
litellm-proxy-extras==0.4.61 # for proxy extras - e.g. prisma migrations
|
||||
llm-sandbox==0.3.31 # for skill execution in sandbox
|
||||
### LITELLM PACKAGE DEPENDENCIES
|
||||
python-dotenv==1.0.1 # for env
|
||||
|
||||
@ -320,6 +320,15 @@ model LiteLLM_MCPServerTable {
|
||||
is_byok Boolean @default(false)
|
||||
byok_description String[] @default([])
|
||||
byok_api_key_help_url String?
|
||||
source_url String?
|
||||
// BYOM submission lifecycle
|
||||
approval_status String? @default("active")
|
||||
submitted_by String?
|
||||
submitted_at DateTime?
|
||||
reviewed_at DateTime?
|
||||
review_notes String?
|
||||
|
||||
@@index([approval_status])
|
||||
}
|
||||
|
||||
// Per-user BYOK credentials for MCP servers
|
||||
|
||||
@ -222,6 +222,27 @@ class TestMigrationSQLIdempotency:
|
||||
+ "\n".join(violations)
|
||||
)
|
||||
|
||||
_DROP_COLUMN_ALLOWLIST = {
|
||||
"20250918083359_drop_spec_version_column_from_mcp_table",
|
||||
"20260213170952_access_group_change_to_model_name",
|
||||
"20260224203854_add_agent_object_permissions_table",
|
||||
}
|
||||
|
||||
def test_no_drop_column_statements(self, all_migrations):
|
||||
"""Migrations must not drop columns — dropping columns is destructive
|
||||
and can break running application instances during rolling deploys."""
|
||||
violations = []
|
||||
for migration_name, sql in all_migrations:
|
||||
if migration_name in self._DROP_COLUMN_ALLOWLIST:
|
||||
continue
|
||||
for line_num, line in enumerate(sql.splitlines(), 1):
|
||||
if re.search(r"DROP\s+COLUMN", line, re.IGNORECASE):
|
||||
violations.append(f" {migration_name}:{line_num}: {line.strip()}")
|
||||
assert not violations, (
|
||||
"DROP COLUMN found in migrations (destructive, not allowed):\n"
|
||||
+ "\n".join(violations)
|
||||
)
|
||||
|
||||
def test_drop_index_uses_if_exists(self, all_migrations):
|
||||
"""DROP INDEX statements must use IF EXISTS"""
|
||||
violations = []
|
||||
|
||||
@ -7288,12 +7288,11 @@ export const getTeamPermissionsCall = async (accessToken: string, teamId: string
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
const errorMessage = deriveErrorMessage(errorData);
|
||||
handleError(errorMessage);
|
||||
throw new Error(errorMessage);
|
||||
console.error("Available permissions fetch failed:", errorMessage);
|
||||
return { all_available_permissions: [], team_member_permissions: [] };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log("Team permissions response:", data);
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Failed to get team permissions:", error);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user