Skip to content

CI

CI #304

Workflow file for this run

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