From 05ffd4475222d1ad9ba93a899a523d5adfb0478b Mon Sep 17 00:00:00 2001 From: Davide Silvestri <75379892+silvestrid@users.noreply.github.com> Date: Tue, 31 Mar 2026 09:49:23 +0200 Subject: [PATCH] chore(ci): fix for external contributors (#5040) * fix: support CI for fork PRs via artifact-based image transfer Fork PRs cannot push Docker images to the org's container registry (GITHUB_TOKEN lacks packages:write for forks). This changes the CI to detect fork PRs and transfer images via GHA artifacts instead of the registry. - Build jobs detect forks and use load (local) instead of push (registry) - Images are saved as gzipped tarballs and uploaded as artifacts - Downstream jobs download and load from artifacts for fork PRs - GHA cache-to is skipped for fork PRs (no write access) For non-fork PRs, behavior is completely unchanged. * test: force is_fork=true to test artifact path REVERT THIS COMMIT before merging. * fix: add pytest.skip to flaky test test_async_start_workflow_rate_limited_runs_eventually_disable_workflow * Revert "fix: add pytest.skip to flaky test test_async_start_workflow_rate_limited_runs_eventually_disable_workflow" This reverts commit 2cd4c2ea76b87083642d9e6124bff771f93f278a. * Revert "test: force is_fork=true to test artifact path" This reverts commit 5f5316b2cba66d4e72fa8c02e99dcb039bee14d6. --- .github/workflows/ci.yml | 129 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 862cc7ed12..bea43db9b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,14 +67,26 @@ jobs: packages: write outputs: image: ${{ steps.image.outputs.full }} + is_fork: ${{ steps.fork-check.outputs.is_fork }} steps: - name: Checkout code uses: actions/checkout@v4 + - name: Check if PR is from a fork + id: fork-check + run: | + if [[ "${{ github.event_name }}" == "pull_request" && \ + "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then + echo "is_fork=true" >> "$GITHUB_OUTPUT" + else + echo "is_fork=false" >> "$GITHUB_OUTPUT" + fi + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to GitHub Container Registry + if: steps.fork-check.outputs.is_fork != 'true' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} @@ -95,15 +107,28 @@ jobs: context: . file: backend/Dockerfile target: ci - push: true + push: ${{ steps.fork-check.outputs.is_fork != 'true' }} + load: ${{ steps.fork-check.outputs.is_fork == 'true' }} tags: ${{ steps.image.outputs.full }} cache-from: ${{ inputs.clear_cache != true && 'type=gha,scope=backend-ci' || '' }} - cache-to: type=gha,scope=backend-ci,mode=max + cache-to: ${{ steps.fork-check.outputs.is_fork != 'true' && 'type=gha,scope=backend-ci,mode=max' || '' }} labels: | org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} org.opencontainers.image.revision=${{ env.REAL_GITHUB_SHA }} org.opencontainers.image.created=${{ github.event.head_commit.timestamp }} + - name: Save Docker image for fork PRs + if: steps.fork-check.outputs.is_fork == 'true' + run: docker save ${{ steps.image.outputs.full }} | gzip > /tmp/backend-ci-image.tar.gz + + - name: Upload Docker image artifact + if: steps.fork-check.outputs.is_fork == 'true' + uses: actions/upload-artifact@v4 + with: + name: backend-ci-image + path: /tmp/backend-ci-image.tar.gz + retention-days: 1 + build-frontend: name: Build Web-Frontend CI Image runs-on: ubuntu-latest @@ -112,14 +137,26 @@ jobs: packages: write outputs: image: ${{ steps.image.outputs.full }} + is_fork: ${{ steps.fork-check.outputs.is_fork }} steps: - name: Checkout code uses: actions/checkout@v4 + - name: Check if PR is from a fork + id: fork-check + run: | + if [[ "${{ github.event_name }}" == "pull_request" && \ + "${{ github.event.pull_request.head.repo.full_name }}" != "${{ github.repository }}" ]]; then + echo "is_fork=true" >> "$GITHUB_OUTPUT" + else + echo "is_fork=false" >> "$GITHUB_OUTPUT" + fi + - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Log in to GitHub Container Registry + if: steps.fork-check.outputs.is_fork != 'true' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} @@ -140,15 +177,28 @@ jobs: context: . file: web-frontend/Dockerfile target: ci - push: true + push: ${{ steps.fork-check.outputs.is_fork != 'true' }} + load: ${{ steps.fork-check.outputs.is_fork == 'true' }} tags: ${{ steps.image.outputs.full }} cache-from: ${{ inputs.clear_cache != true && 'type=gha,scope=frontend-ci' || '' }} - cache-to: type=gha,scope=frontend-ci,mode=max + cache-to: ${{ steps.fork-check.outputs.is_fork != 'true' && 'type=gha,scope=frontend-ci,mode=max' || '' }} labels: | org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }} org.opencontainers.image.revision=${{ env.REAL_GITHUB_SHA }} org.opencontainers.image.created=${{ github.event.head_commit.timestamp }} + - name: Save Docker image for fork PRs + if: steps.fork-check.outputs.is_fork == 'true' + run: docker save ${{ steps.image.outputs.full }} | gzip > /tmp/frontend-ci-image.tar.gz + + - name: Upload Docker image artifact + if: steps.fork-check.outputs.is_fork == 'true' + uses: actions/upload-artifact@v4 + with: + name: frontend-ci-image + path: /tmp/frontend-ci-image.tar.gz + retention-days: 1 + # ========================================================================== # LINT STAGE - Run linting on backend, frontend, and Dockerfiles # ========================================================================== @@ -228,12 +278,24 @@ jobs: packages: read steps: - name: Log in to GitHub Container Registry + if: needs.build-frontend.outputs.is_fork != 'true' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Download frontend image artifact + if: needs.build-frontend.outputs.is_fork == 'true' + uses: actions/download-artifact@v4 + with: + name: frontend-ci-image + path: /tmp + + - name: Load frontend image from artifact + if: needs.build-frontend.outputs.is_fork == 'true' + run: gunzip -c /tmp/frontend-ci-image.tar.gz | docker load + - name: Run frontend lint run: | docker run --rm \ @@ -340,12 +402,24 @@ jobs: --shm-size=512m steps: - name: Log in to GitHub Container Registry + if: needs.build-backend.outputs.is_fork != 'true' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Download backend image artifact + if: needs.build-backend.outputs.is_fork == 'true' + uses: actions/download-artifact@v4 + with: + name: backend-ci-image + path: /tmp + + - name: Load backend image from artifact + if: needs.build-backend.outputs.is_fork == 'true' + run: gunzip -c /tmp/backend-ci-image.tar.gz | docker load + - name: Check backend startup run: | docker run --rm --network="${{ job.services.db.network }}" \ @@ -405,12 +479,24 @@ jobs: --shm-size=512m steps: - name: Log in to GitHub Container Registry + if: needs.build-backend.outputs.is_fork != 'true' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Download backend image artifact + if: needs.build-backend.outputs.is_fork == 'true' + uses: actions/download-artifact@v4 + with: + name: backend-ci-image + path: /tmp + + - name: Load backend image from artifact + if: needs.build-backend.outputs.is_fork == 'true' + run: gunzip -c /tmp/backend-ci-image.tar.gz | docker load + - name: Run backend tests for group ${{ matrix.group }} run: | mkdir -p reports @@ -464,12 +550,24 @@ jobs: shard: [1, 2, 3] steps: - name: Log in to GitHub Container Registry + if: needs.build-frontend.outputs.is_fork != 'true' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Download frontend image artifact + if: needs.build-frontend.outputs.is_fork == 'true' + uses: actions/download-artifact@v4 + with: + name: frontend-ci-image + path: /tmp + + - name: Load frontend image from artifact + if: needs.build-frontend.outputs.is_fork == 'true' + run: gunzip -c /tmp/frontend-ci-image.tar.gz | docker load + - name: Run web-frontend tests for shard ${{ matrix.shard }} env: CI: "true" @@ -621,12 +719,35 @@ jobs: cache-dependency-path: "e2e-tests/yarn.lock" - name: Log in to GitHub Container Registry + if: needs.build-backend.outputs.is_fork != 'true' uses: docker/login-action@v3 with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} + - name: Download backend image artifact + if: needs.build-backend.outputs.is_fork == 'true' + uses: actions/download-artifact@v4 + with: + name: backend-ci-image + path: /tmp + + - name: Load backend image from artifact + if: needs.build-backend.outputs.is_fork == 'true' + run: gunzip -c /tmp/backend-ci-image.tar.gz | docker load + + - name: Download frontend image artifact + if: needs.build-frontend.outputs.is_fork == 'true' + uses: actions/download-artifact@v4 + with: + name: frontend-ci-image + path: /tmp + + - name: Load frontend image from artifact + if: needs.build-frontend.outputs.is_fork == 'true' + run: gunzip -c /tmp/frontend-ci-image.tar.gz | docker load + - name: Restore database from dump run: | echo "Restoring database from dump to container ${{ job.services.db.id }}..."