# ObsiGate CI/CD Pipeline # Runs on every push and pull request to main name: CI on: push: branches: [main] pull_request: branches: [main] jobs: # ── Lint ────────────────────────────────────────────────────────── lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install dependencies run: | pip install ruff mypy pip install -r backend/requirements.txt - name: Ruff (linter) run: ruff check backend/ - name: Mypy (type checker) run: mypy backend/ --ignore-missing-imports || echo "mypy found type errors (advisory — 28 pre-existing issues)" - name: Frontend validation run: node tests/frontend/validate-imports.mjs - name: Frontend unit tests run: node tests/frontend/unit.test.mjs # ── Tests ───────────────────────────────────────────────────────── test: needs: lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install dependencies run: | pip install pytest pytest-cov pytest-asyncio httpx pip install -r backend/requirements.txt - name: Run tests run: pytest tests/ --cov=backend --cov-report=xml --cov-report=term -q - name: Upload coverage artifact uses: actions/upload-artifact@v3 with: name: coverage-report path: coverage.xml retention-days: 30 # ── Security scan ───────────────────────────────────────────────── security: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Python uses: actions/setup-python@v5 with: python-version: "3.11" - name: Install dependencies run: | pip install bandit pip-audit pip install -r backend/requirements.txt - name: Bandit (SAST) run: bandit -r backend/ --skip B101,B110,B310 || echo "bandit found issues (non-blocking)" - name: Pip-audit (dependency vulnerabilities) run: pip-audit || echo "pip-audit found vulnerabilities (non-blocking)" # ── Docker build ────────────────────────────────────────────────── build: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Generate VERSION file run: | VERSION=$(git describe --tags --dirty 2>/dev/null | sed 's/^v//' || echo "0.0.0-dev") echo "$VERSION" > backend/VERSION - name: Build Docker image run: docker build -t obsigate:ci . - name: Verify image run: docker images obsigate:ci # ── E2E Tests (Playwright) ───────────────────────────────────────── e2e: needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: "20" - name: Install Playwright run: | npm ci npx playwright install --with-deps chromium - name: Start ObsiGate run: | docker rm -f obsigate-e2e 2>/dev/null || true docker create --name obsigate-e2e -p 2029:8080 \ -v $(pwd)/test_vault:/vaults/TestVault \ -v $(pwd)/test_dir:/vaults/TestDir \ -e VAULT_1_NAME=TestVault \ -e VAULT_1_PATH=/vaults/TestVault \ -e DIR_1_NAME=TestDir \ -e DIR_1_PATH=/vaults/TestDir \ -e OBSIGATE_AUTH_ENABLED=false \ obsigate:ci # Docker-in-docker : le bind mount $(pwd)/... pointe sur un chemin # du job container, inexistant sur l'hôte → montage vide. Les -v # créent quand même /vaults/* dans le container ; on y copie les # fixtures avant le démarrage (l'indexeur scanne au démarrage). docker cp test_vault/. obsigate-e2e:/vaults/TestVault docker cp test_dir/. obsigate-e2e:/vaults/TestDir docker start obsigate-e2e # Le port publié est joignable via l'IP de la passerelle (localhost # du job container ne voit pas le port publié sur l'hôte). GW=$(ip route show default | awk '{print $3}' || echo 172.17.0.1) echo "Gateway IP: $GW" # Wait for health check for i in $(seq 1 30); do if curl -sf "http://$GW:2029/api/health"; then echo "Health OK"; break; fi sleep 1 done curl -sf "http://$GW:2029/api/health" >/dev/null || { echo "App not reachable at $GW:2029"; exit 1; } - name: Run E2E tests run: | GW=$(ip route show default | awk '{print $3}' || echo 172.17.0.1) echo "Using BASE_URL=http://$GW:2029" BASE_URL="http://$GW:2029" npx playwright test --project=chromium-desktop --reporter=list - name: Upload test results if: always() uses: actions/upload-artifact@v3 with: name: playwright-report path: playwright-report/ retention-days: 7 - name: Cleanup if: always() run: docker rm -f obsigate-e2e