feat: Implement linked file management in FileService #5
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: CI | |
| on: | |
| pull_request: | |
| branches: [main, dev] | |
| push: | |
| branches: [main, dev] | |
| tags: ["v*.*.*"] | |
| merge_group: | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| packages: read | |
| env: | |
| PYTHON_VERSION: "3.11" | |
| REGISTRY_IMAGE: ghcr.io/usnavy13/librecodeinterpreter | |
| RUNTIME_R_IMAGE: ghcr.io/usnavy13/librecodeinterpreter/runtime-r | |
| BUILDCACHE_IMAGE: ghcr.io/usnavy13/librecodeinterpreter/buildcache | |
| LOCAL_API_IMAGE_AMD64: code-interpreter:ci-amd64 | |
| LOCAL_API_IMAGE_ARM64: code-interpreter:ci-arm64 | |
| jobs: | |
| changes: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| runtime: ${{ steps.filter.outputs.runtime }} | |
| container: ${{ steps.filter.outputs.container }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - id: filter | |
| uses: dorny/paths-filter@v3 | |
| with: | |
| filters: | | |
| runtime: | |
| - 'Dockerfile' | |
| - 'docker/requirements/**' | |
| container: | |
| - 'Dockerfile' | |
| - 'docker/**' | |
| - 'src/**' | |
| - 'dashboard/**' | |
| - 'requirements.txt' | |
| - 'docker-compose.yml' | |
| static: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: pip | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| pip install flake8 black mypy bandit | |
| - name: Lint with flake8 | |
| run: | | |
| flake8 src/ --count --select=E9,F63,F7,F82 --show-source --statistics | |
| flake8 src/ --count --exit-zero --max-complexity=10 --statistics | |
| - name: Check formatting with Black | |
| run: black src/ --check | |
| - name: Type checking with mypy | |
| run: mypy src/ | |
| - name: Security scan with Bandit | |
| run: bandit -r src/ -s B104,B108 --severity-level high | |
| unit: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: pip | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| pip install pytest pytest-asyncio pytest-cov pytest-mock | |
| - name: Run unit tests | |
| run: | | |
| mkdir -p test-results | |
| pytest tests/unit/ --junitxml=test-results/unit.xml | |
| - name: Upload unit results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: unit-results | |
| path: test-results/ | |
| if-no-files-found: ignore | |
| integration-contract: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: pip | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| pip install pytest pytest-asyncio pytest-cov pytest-mock | |
| - name: Run contract integration tests | |
| run: | | |
| mkdir -p test-results | |
| pytest tests/integration/ -m contract_only --junitxml=test-results/integration-contract.xml | |
| - name: Upload contract integration results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: integration-contract-results | |
| path: test-results/ | |
| if-no-files-found: ignore | |
| integration-core: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: pip | |
| - name: Install dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| pip install pytest pytest-asyncio pytest-cov pytest-mock | |
| - name: Run core integration tests | |
| run: | | |
| mkdir -p test-results | |
| pytest tests/integration/ -m "not contract_only" --junitxml=test-results/integration-core.xml | |
| - name: Upload core integration results | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: integration-core-results | |
| path: test-results/ | |
| if-no-files-found: ignore | |
| build-app-amd64: | |
| needs: [changes] | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: docker/setup-buildx-action@v3 | |
| - uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - id: runtime | |
| name: Compute runtime base | |
| run: | | |
| runtime_hash="$(scripts/ci/compute_runtime_hash.sh)" | |
| runtime_base="$(scripts/ci/resolve_runtime_base.sh "${RUNTIME_R_IMAGE}" "${runtime_hash}")" | |
| echo "runtime_hash=${runtime_hash}" >> "${GITHUB_OUTPUT}" | |
| echo "runtime_base=${runtime_base}" >> "${GITHUB_OUTPUT}" | |
| - name: Build amd64 app candidate | |
| run: | | |
| docker buildx build \ | |
| --load \ | |
| --target app \ | |
| --tag "${LOCAL_API_IMAGE_AMD64}" \ | |
| --build-arg "RUNTIME_R_BASE=${{ steps.runtime.outputs.runtime_base }}" \ | |
| --cache-from "type=gha,scope=app-amd64" \ | |
| --cache-from "type=registry,ref=${BUILDCACHE_IMAGE}:runtime-r-amd64" \ | |
| --cache-to "type=gha,scope=app-amd64,mode=max" \ | |
| . | |
| - name: Inspect amd64 image | |
| run: docker image inspect "${LOCAL_API_IMAGE_AMD64}" >/dev/null | |
| functional-smoke-amd64: | |
| needs: [changes] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: pip | |
| - uses: docker/setup-buildx-action@v3 | |
| - uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Install test dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| pip install pytest pytest-asyncio pytest-cov pytest-mock | |
| - id: runtime | |
| name: Compute runtime base | |
| run: | | |
| runtime_hash="$(scripts/ci/compute_runtime_hash.sh)" | |
| runtime_base="$(scripts/ci/resolve_runtime_base.sh "${RUNTIME_R_IMAGE}" "${runtime_hash}")" | |
| echo "runtime_base=${runtime_base}" >> "${GITHUB_OUTPUT}" | |
| - name: Build local amd64 test image | |
| run: | | |
| docker buildx build \ | |
| --load \ | |
| --target app \ | |
| --tag "${LOCAL_API_IMAGE_AMD64}" \ | |
| --build-arg "RUNTIME_R_BASE=${{ steps.runtime.outputs.runtime_base }}" \ | |
| --cache-from "type=gha,scope=app-amd64" \ | |
| --cache-from "type=registry,ref=${BUILDCACHE_IMAGE}:runtime-r-amd64" \ | |
| --cache-to "type=gha,scope=app-amd64,mode=max" \ | |
| . | |
| - name: Start live stack | |
| env: | |
| API_IMAGE: ${{ env.LOCAL_API_IMAGE_AMD64 }} | |
| run: | | |
| cp .env.example .env | |
| docker compose up -d | |
| - name: Wait for API | |
| run: | | |
| if ! scripts/ci/wait_for_api.sh http://localhost:8000/health 24 5; then | |
| docker compose logs --no-color api | |
| exit 1 | |
| fi | |
| - name: Run live smoke tests | |
| env: | |
| API_BASE: http://localhost:8000 | |
| API_KEY: your-secure-api-key-here-change-this-in-production | |
| run: | | |
| mkdir -p test-results | |
| pytest tests/functional/ \ | |
| -m "live_api and not slow and not client_replay" \ | |
| -v \ | |
| --junitxml=test-results/functional-smoke-amd64.xml | |
| - name: Capture compose logs on failure | |
| if: failure() | |
| run: docker compose logs --no-color > compose-amd64.log | |
| - name: Upload functional smoke artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: functional-smoke-amd64 | |
| path: | | |
| test-results/ | |
| compose-amd64.log | |
| if-no-files-found: ignore | |
| - name: Stop live stack | |
| if: always() | |
| run: docker compose down -v | |
| client-replay-amd64: | |
| needs: [changes] | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: pip | |
| - uses: docker/setup-buildx-action@v3 | |
| - uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Install test dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| pip install pytest pytest-asyncio pytest-cov pytest-mock | |
| - id: runtime | |
| name: Compute runtime base | |
| run: | | |
| runtime_hash="$(scripts/ci/compute_runtime_hash.sh)" | |
| runtime_base="$(scripts/ci/resolve_runtime_base.sh "${RUNTIME_R_IMAGE}" "${runtime_hash}")" | |
| echo "runtime_base=${runtime_base}" >> "${GITHUB_OUTPUT}" | |
| - name: Build local amd64 replay image | |
| run: | | |
| docker buildx build \ | |
| --load \ | |
| --target app \ | |
| --tag "${LOCAL_API_IMAGE_AMD64}" \ | |
| --build-arg "RUNTIME_R_BASE=${{ steps.runtime.outputs.runtime_base }}" \ | |
| --cache-from "type=gha,scope=app-amd64" \ | |
| --cache-from "type=registry,ref=${BUILDCACHE_IMAGE}:runtime-r-amd64" \ | |
| --cache-to "type=gha,scope=app-amd64,mode=max" \ | |
| . | |
| - name: Start live stack | |
| env: | |
| API_IMAGE: ${{ env.LOCAL_API_IMAGE_AMD64 }} | |
| run: | | |
| cp .env.example .env | |
| docker compose up -d | |
| - name: Wait for API | |
| run: | | |
| if ! scripts/ci/wait_for_api.sh http://localhost:8000/health 24 5; then | |
| docker compose logs --no-color api | |
| exit 1 | |
| fi | |
| - name: Run client replay tests | |
| env: | |
| API_BASE: http://localhost:8000 | |
| API_KEY: your-secure-api-key-here-change-this-in-production | |
| run: | | |
| mkdir -p test-results | |
| pytest tests/functional/ \ | |
| -m client_replay \ | |
| -v \ | |
| --junitxml=test-results/client-replay-amd64.xml | |
| - name: Capture compose logs on failure | |
| if: failure() | |
| run: docker compose logs --no-color > compose-client-replay.log | |
| - name: Upload client replay artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: client-replay-amd64 | |
| path: | | |
| test-results/ | |
| compose-client-replay.log | |
| if-no-files-found: ignore | |
| - name: Stop live stack | |
| if: always() | |
| run: docker compose down -v | |
| arm64-smoke-conditional: | |
| if: needs.changes.outputs.container == 'true' | |
| needs: [changes] | |
| runs-on: ubuntu-24.04-arm | |
| timeout-minutes: 35 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION }} | |
| cache: pip | |
| - uses: docker/setup-buildx-action@v3 | |
| - uses: docker/login-action@v3 | |
| with: | |
| registry: ghcr.io | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Install test dependencies | |
| run: | | |
| python -m pip install --upgrade pip | |
| pip install -r requirements.txt | |
| pip install pytest pytest-asyncio pytest-cov pytest-mock | |
| - id: runtime | |
| name: Compute runtime base | |
| run: | | |
| runtime_hash="$(scripts/ci/compute_runtime_hash.sh)" | |
| runtime_base="$(scripts/ci/resolve_runtime_base.sh "${RUNTIME_R_IMAGE}" "${runtime_hash}")" | |
| echo "runtime_base=${runtime_base}" >> "${GITHUB_OUTPUT}" | |
| - name: Build local arm64 test image | |
| run: | | |
| docker buildx build \ | |
| --load \ | |
| --target app \ | |
| --tag "${LOCAL_API_IMAGE_ARM64}" \ | |
| --build-arg "RUNTIME_R_BASE=${{ steps.runtime.outputs.runtime_base }}" \ | |
| --cache-from "type=gha,scope=app-arm64" \ | |
| --cache-from "type=registry,ref=${BUILDCACHE_IMAGE}:runtime-r-arm64" \ | |
| --cache-to "type=gha,scope=app-arm64,mode=max" \ | |
| . | |
| - name: Start live stack | |
| env: | |
| API_IMAGE: ${{ env.LOCAL_API_IMAGE_ARM64 }} | |
| run: | | |
| cp .env.example .env | |
| docker compose up -d | |
| - name: Wait for API | |
| run: | | |
| if ! scripts/ci/wait_for_api.sh http://localhost:8000/health 24 5; then | |
| docker compose logs --no-color api | |
| exit 1 | |
| fi | |
| - name: Run arm64 smoke suite | |
| env: | |
| API_BASE: http://localhost:8000 | |
| API_KEY: your-secure-api-key-here-change-this-in-production | |
| run: | | |
| mkdir -p test-results | |
| pytest \ | |
| tests/functional/test_health.py \ | |
| tests/functional/test_exec_workflow.py::TestSessionWorkflow::test_execution_creates_session \ | |
| tests/functional/test_files.py::TestFileUpload::test_upload_single_file \ | |
| tests/functional/test_ptc.py::TestPTCInitialExecution::test_ptc_simple_code_completes \ | |
| -v \ | |
| --junitxml=test-results/arm64-smoke.xml | |
| - name: Capture compose logs on failure | |
| if: failure() | |
| run: docker compose logs --no-color > compose-arm64.log | |
| - name: Upload arm64 smoke artifacts | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: arm64-smoke | |
| path: | | |
| test-results/ | |
| compose-arm64.log | |
| if-no-files-found: ignore | |
| - name: Stop live stack | |
| if: always() | |
| run: docker compose down -v |