Flatten published images so upstream enterprise layers are not distri… #8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: build | |
| on: | |
| schedule: | |
| - cron: "23 5 * * *" # daily: pick up new upstream stable releases | |
| push: | |
| branches: [main] | |
| paths: | |
| - Dockerfile | |
| - patch/** | |
| - .github/workflows/build.yml | |
| workflow_dispatch: | |
| inputs: | |
| litellm_version: | |
| description: "LiteLLM release tag (e.g. v1.93.0). Empty = latest stable release." | |
| required: false | |
| permissions: | |
| contents: read | |
| packages: write | |
| actions: read # notify job reads previous run conclusions | |
| concurrency: | |
| group: build | |
| cancel-in-progress: false | |
| jobs: | |
| build: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # plain image: consumed by litellm-lagoon (Lagoon/docker-compose) | |
| - upstream: litellm | |
| package: litellm-lagoon-base | |
| # -database image: consumed by the litellm helm chart (k0rdent) | |
| - upstream: litellm-database | |
| package: litellm-lagoon-base-database | |
| env: | |
| IMAGE: ghcr.io/${{ github.repository_owner }}/${{ matrix.package }} | |
| # The docker build pushes here first. Its result still DISTRIBUTES the | |
| # enterprise code: `rm -rf` in the Dockerfile only masks files in the | |
| # top layer, the inherited upstream layers keep the content. The | |
| # publish step crane-flattens staging into a single layer at $IMAGE, | |
| # so only the post-rm filesystem is ever public. The -staging packages | |
| # must therefore STAY PRIVATE. | |
| STAGING: ghcr.io/${{ github.repository_owner }}/${{ matrix.package }}-staging | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Resolve LiteLLM version | |
| id: version | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| VERSION='${{ inputs.litellm_version }}' | |
| if [ -z "$VERSION" ]; then | |
| VERSION="$(gh api repos/BerriAI/litellm/releases --jq '[.[] | select(.prerelease | not)][0].tag_name')" | |
| fi | |
| [ -n "$VERSION" ] || { echo "could not resolve a LiteLLM version"; exit 1; } | |
| echo "version=$VERSION" | tee -a "$GITHUB_OUTPUT" | |
| - uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| # On scheduled runs, skip versions we already published. Pushes to | |
| # main (patch/Dockerfile changes) always republish the current version. | |
| - name: Check if already published | |
| id: published | |
| if: github.event_name == 'schedule' | |
| run: | | |
| if docker manifest inspect "$IMAGE:${{ steps.version.outputs.version }}" >/dev/null 2>&1; then | |
| echo "exists=true" | tee -a "$GITHUB_OUTPUT" | |
| fi | |
| # GHCR shows org.opencontainers.image.description as the package | |
| # description; build it from the patch filenames so the package page | |
| # lists what was applied. | |
| - name: Describe patches | |
| id: patches | |
| run: | | |
| PATCHES="$(ls patch/*.patch 2>/dev/null | xargs -rn1 basename | sed 's/\.patch$//' | paste -sd, -)" | |
| echo "description=LiteLLM ${{ steps.version.outputs.version }}, enterprise code stripped, patches: ${PATCHES:-none}" | tee -a "$GITHUB_OUTPUT" | |
| - uses: docker/setup-qemu-action@v3 | |
| if: steps.published.outputs.exists != 'true' | |
| - uses: docker/setup-buildx-action@v3 | |
| if: steps.published.outputs.exists != 'true' | |
| - uses: docker/build-push-action@v6 | |
| if: steps.published.outputs.exists != 'true' | |
| with: | |
| context: . | |
| push: true | |
| platforms: linux/amd64,linux/arm64 | |
| build-args: | | |
| LITELLM_IMAGE=ghcr.io/berriai/${{ matrix.upstream }} | |
| LITELLM_VERSION=${{ steps.version.outputs.version }} | |
| # No provenance attestations: crane flatten drops them anyway. | |
| provenance: false | |
| tags: | | |
| ${{ env.STAGING }}:${{ steps.version.outputs.version }} | |
| # GHCR reads the description from the image index annotation on | |
| # multi-arch images; the label covers single-manifest consumers. | |
| labels: | | |
| org.opencontainers.image.description=${{ steps.patches.outputs.description }} | |
| org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} | |
| annotations: | | |
| index:org.opencontainers.image.description=${{ steps.patches.outputs.description }} | |
| index:org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} | |
| - uses: imjasonh/setup-crane@v0.4 | |
| if: steps.published.outputs.exists != 'true' | |
| # Squash to a single layer per arch so the upstream layers containing | |
| # the enterprise code are not part of the public image. Flatten | |
| # preserves the image config (env, entrypoint, labels) and the index | |
| # annotations; crane reuses the docker login from above. | |
| - name: Flatten into public image | |
| if: steps.published.outputs.exists != 'true' | |
| run: | | |
| crane flatten "$STAGING:${{ steps.version.outputs.version }}" -t "$IMAGE:${{ steps.version.outputs.version }}" | |
| crane tag "$IMAGE:${{ steps.version.outputs.version }}" latest | |
| # One Slack message per breakage, not per matrix leg or per scheduled | |
| # retry: skip posting when the previous completed run also failed. | |
| notify: | |
| runs-on: ubuntu-latest | |
| needs: build | |
| if: failure() | |
| steps: | |
| - name: Check previous run conclusion | |
| id: prev | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| PREV="$(gh api "repos/${{ github.repository }}/actions/workflows/build.yml/runs?status=completed&per_page=1" --jq '.workflow_runs[0].conclusion')" | |
| echo "conclusion=$PREV" | tee -a "$GITHUB_OUTPUT" | |
| - name: Post to Slack | |
| if: steps.prev.outputs.conclusion != 'failure' | |
| env: | |
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} | |
| SLACK_CHANNEL_ID: ${{ vars.SLACK_CHANNEL_ID }} | |
| TEXT: ":rotating_light: litellm-lagoon-base build failed for LiteLLM ${{ needs.build.outputs.version || 'unknown' }} — the patch may no longer apply. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" | |
| run: | | |
| curl -fsS -X POST https://slack.com/api/chat.postMessage \ | |
| -H "Authorization: Bearer $SLACK_BOT_TOKEN" \ | |
| -H 'Content-type: application/json; charset=utf-8' \ | |
| --data "$(jq -n --arg c "$SLACK_CHANNEL_ID" --arg t "$TEXT" '{channel:$c,text:$t}')" \ | |
| | jq -e '.ok' |