CI #307
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, master] | |
| push: | |
| branches: [main, master] | |
| schedule: | |
| - cron: "0 2 * * *" # Nightly at 2 AM UTC | |
| workflow_dispatch: | |
| inputs: | |
| python-version: | |
| description: 'Python version (or "all" for matrix)' | |
| required: false | |
| default: "3.13" | |
| db-version: | |
| description: "Exasol DB version" | |
| required: false | |
| default: "8.29.13" | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| DBT_DSN: localhost/nocertcheck:8563 | |
| DBT_USER: sys | |
| DBT_PASS: exasol | |
| DBT_TEST_USER_1: dbt_test_role_1 | |
| DBT_TEST_USER_2: dbt_test_role_2 | |
| DBT_TEST_USER_3: dbt_test_role_3 | |
| EXASOL_RELEASE: "8" | |
| DBT_CONN_POOL_SIZE: 20 | |
| DBT_SEND_ANONYMOUS_USAGE_STATS: false | |
| jobs: | |
| setup: | |
| name: Setup | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| python-matrix: ${{ steps.matrix.outputs.versions }} | |
| integration-matrix: ${{ steps.integration-matrix.outputs.versions }} | |
| run-integration: ${{ steps.should-run-integration.outputs.run }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Detect path changes | |
| uses: dorny/paths-filter@v3 | |
| id: changes | |
| with: | |
| filters: | | |
| integration: | |
| - 'dbt/adapters/exasol/**' | |
| - 'dbt/include/**' | |
| - 'tests/functional/**' | |
| - 'tests/conftest.py' | |
| - 'noxfile.py' | |
| - 'noxconfig.py' | |
| - 'pyproject.toml' | |
| - name: Determine if integration should run | |
| id: should-run-integration | |
| run: | | |
| # Always run for scheduled builds and manual workflow dispatch | |
| if [[ "${{ github.event_name }}" == "schedule" ]] || \ | |
| [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| echo "run=true" >> $GITHUB_OUTPUT | |
| # For push to main/master, always run integration tests | |
| elif [[ "${{ github.event_name }}" == "push" ]] && \ | |
| [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.ref }}" == "refs/heads/master" ]]; then | |
| echo "run=true" >> $GITHUB_OUTPUT | |
| # For PRs, use path-based filtering | |
| else | |
| echo "run=${{ steps.changes.outputs.integration }}" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Setup mise | |
| uses: jdx/mise-action@v2 | |
| with: | |
| experimental: true | |
| - name: Cache mise | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.local/share/mise | |
| key: mise-${{ runner.os }}-${{ hashFiles('mise.toml') }} | |
| - name: Generate Python matrix (checks) | |
| id: matrix | |
| run: echo "versions=$(mise run nox -- -s matrix:python)" >> $GITHUB_OUTPUT | |
| - name: Generate Python matrix (integration) | |
| id: integration-matrix | |
| run: | | |
| if [[ "${{ github.event_name }}" == "schedule" ]] || \ | |
| [[ "${{ github.event_name }}" == "push" ]] || \ | |
| [[ "${{ github.event.inputs.python-version }}" == "all" ]]; then | |
| # Full matrix for scheduled/push to main/manual "all" | |
| echo "versions=$(mise run nox -- -s matrix:python)" >> $GITHUB_OUTPUT | |
| else | |
| # Single version for PRs (fast feedback) | |
| VERSION="${{ github.event.inputs.python-version || '3.13' }}" | |
| echo "versions={\"python-version\":[\"$VERSION\"]}" >> $GITHUB_OUTPUT | |
| fi | |
| checks: | |
| name: Checks (Python-${{ matrix.python-version }}) | |
| needs: setup | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.setup.outputs.python-matrix) }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup mise | |
| uses: jdx/mise-action@v2 | |
| with: | |
| experimental: true | |
| - name: Cache mise | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.local/share/mise | |
| key: mise-${{ runner.os }}-${{ hashFiles('mise.toml') }} | |
| - name: Cache venv | |
| uses: actions/cache@v4 | |
| with: | |
| path: .venv | |
| key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('uv.lock') }} | |
| - name: Install dependencies | |
| run: mise run sync -- --all-extras | |
| - name: Format Check | |
| run: mise run format-check | |
| - name: Lint Code | |
| run: mise run nox -- -s lint:code | |
| - name: Lint Security | |
| run: mise run nox -- -s lint:security | |
| - name: Type Check | |
| run: mise run nox -- -s lint:typing | |
| - name: Deprecation Audit | |
| run: mise run nox -- -s lint:deprecations | |
| - name: Unit Tests | |
| run: mise run nox -- -s test:unit -- --coverage | |
| - name: Upload Coverage Artifact | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: coverage-python${{ matrix.python-version }} | |
| path: .coverage | |
| include-hidden-files: true | |
| retention-days: 30 | |
| - name: Upload Lint Artifact | |
| uses: actions/upload-artifact@v5 | |
| if: always() | |
| with: | |
| name: lint-python${{ matrix.python-version }} | |
| path: | | |
| .lint.txt | |
| .lint.json | |
| include-hidden-files: true | |
| retention-days: 30 | |
| - name: Upload Security Artifact | |
| uses: actions/upload-artifact@v5 | |
| if: always() | |
| with: | |
| name: security-python${{ matrix.python-version }} | |
| path: .security.json | |
| include-hidden-files: true | |
| retention-days: 30 | |
| integration: | |
| name: Integration (Python-${{ matrix.python-version }}) | |
| needs: setup | |
| if: needs.setup.outputs.run-integration == 'true' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| strategy: | |
| fail-fast: false | |
| matrix: ${{ fromJSON(needs.setup.outputs.integration-matrix) }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup mise | |
| uses: jdx/mise-action@v2 | |
| with: | |
| python-version: ${{ matrix.python-version }} | |
| - name: Cache venv | |
| uses: actions/cache@v4 | |
| with: | |
| path: .venv | |
| key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}-py${{ matrix.python-version }} | |
| - name: Cache mise | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.local/share/mise | |
| key: mise-cache-${{ runner.os }} | |
| - name: Install dependencies | |
| run: mise run sync | |
| # Exasol script-language UDFs (Python/R/Java) sandbox the exaudfclient | |
| # subprocess in an unprivileged user namespace via `nschroot`. On Ubuntu | |
| # 24.04+ runners the kernel default `kernel.apparmor_restrict_unprivileged_userns=1` | |
| # forces that namespace into a restricted AppArmor profile, killing the | |
| # subprocess on its first IPC recv -> "VM error: Internal error: VM crashed". | |
| # SQL UDFs are unaffected. Disabling the restriction on the host VM fixes it. | |
| # The guard makes this a no-op on older runner images lacking the sysctl. | |
| - name: Allow unprivileged user namespaces (Exasol script-language UDFs) | |
| run: | | |
| if sysctl -n kernel.apparmor_restrict_unprivileged_userns >/dev/null 2>&1; then | |
| sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 | |
| else | |
| echo "sysctl kernel.apparmor_restrict_unprivileged_userns not present; nothing to do" | |
| fi | |
| - name: Run integration tests | |
| run: mise run nox -- -s test:integration -- --coverage -n4 | |
| env: | |
| EXASOL_DB_MEM_SIZE: 4GB | |
| - name: Upload integration coverage | |
| uses: actions/upload-artifact@v5 | |
| if: always() | |
| with: | |
| name: integration-coverage-python${{ matrix.python-version }} | |
| path: .coverage | |
| include-hidden-files: true | |
| retention-days: 7 | |
| report: | |
| name: Report | |
| needs: [checks, integration] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| if: | | |
| always() && | |
| needs.checks.result == 'success' && | |
| (needs.integration.result == 'success' || needs.integration.result == 'skipped') | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Setup mise | |
| uses: jdx/mise-action@v2 | |
| with: | |
| experimental: true | |
| - name: Cache mise | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.local/share/mise | |
| key: mise-${{ runner.os }}-${{ hashFiles('mise.toml') }} | |
| - name: Cache venv | |
| uses: actions/cache@v4 | |
| with: | |
| path: .venv | |
| key: venv-${{ runner.os }}-${{ hashFiles('uv.lock') }} | |
| - name: Install dependencies | |
| run: mise run sync -- --all-extras | |
| - name: Download Artifacts | |
| uses: actions/download-artifact@v6 | |
| with: | |
| path: artifacts | |
| - name: Copy Artifacts | |
| run: mise run nox -- -s artifacts:copy -- artifacts | |
| - name: Sonar Scan | |
| env: | |
| SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: mise run nox -- -s sonar:check |