Repository navigation
chore(release): version 0.1.2 #46
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: Build and Test | |
| # The Trino integration suite runs here, in the integration-tests job below. | |
| # The stack fits a standard runner with room to spare: on ubuntu-24.04 | |
| # (4 vCPU / 16 GB) the coordinator idles at 871 MiB and the host keeps 13 GiB | |
| # of 15 GiB free, so the job needs no reduced logging, no image cache and no | |
| # tuning. The oauth profile stays out, because its login needs a browser. | |
| permissions: | |
| contents: read | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| merge_group: | |
| # Supersede in-flight runs on the same ref. Never cancel in a merge queue: a | |
| # cancelled merge_group run reports failure and evicts the PR from the queue. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_TOOLCHAIN_VERSION: "1.97.1" | |
| # Every job below names a runner image rather than a `-latest` alias, so an | |
| # image roll cannot change what a merge is gated on. The label cannot be lifted | |
| # into a variable: `runs-on` accepts no `env` context, and the one context that | |
| # would work, `vars`, holds its value in repository settings rather than here. | |
| jobs: | |
| # Formatting, clippy (which is what enforces the unwrap_used / | |
| # unwrap_in_result / panic denies from Cargo.toml), cargo-deny and | |
| # cargo-sort. This lives in this workflow rather than its own because | |
| # `needs:` cannot cross workflows, and a lint gate the required check does | |
| # not observe is not a gate. | |
| pre-commit: | |
| name: pre-commit | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| steps: | |
| # The cargo-test pre-commit hook links libodbc via odbc-sys. | |
| - name: Install host dependencies | |
| uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3 | |
| with: | |
| packages: unixodbc-dev | |
| # A cache key, not a runner label, but it tracks the runner image so | |
| # that bumping the image invalidates the cached .deb files. | |
| version: ubuntu-24.04 | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust ${{ env.RUST_TOOLCHAIN_VERSION }} toolchain | |
| uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b # 1.95.0 | |
| with: | |
| toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} | |
| components: rustfmt, clippy | |
| - name: Setup Rust Cache | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| - name: Install cargo-deny and cargo-sort | |
| uses: taiki-e/install-action@cb33e69fad06166ca28a42b2575e4dadabf62ee8 # v2.85.8 | |
| with: | |
| tool: cargo-deny,cargo-sort | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: "3.12" | |
| - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 | |
| unit-tests: | |
| name: Unit Tests | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| steps: | |
| # odbc-sys links against libodbc/libodbcinst, so the unixODBC dev | |
| # libraries must be present to link the test binaries (no running Driver | |
| # Manager is needed — only the libraries). | |
| - name: Install host dependencies | |
| uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3 | |
| with: | |
| packages: unixodbc-dev | |
| # A cache key, not a runner label, but it tracks the runner image so | |
| # that bumping the image invalidates the cached .deb files. | |
| version: ubuntu-24.04 | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust ${{ env.RUST_TOOLCHAIN_VERSION }} toolchain | |
| uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b | |
| with: | |
| toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} | |
| - name: Setup Rust Cache | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| # --locked so CI tests the dependency versions Cargo.lock pins, and so a | |
| # Cargo.toml change without a matching lockfile update fails here instead | |
| # of drifting. | |
| - name: Run unit tests | |
| run: cargo test --locked | |
| unit-tests-windows: | |
| name: Unit Tests (Windows) | |
| runs-on: windows-2022 | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| # aws-lc-sys reaches this crate through trino-rust-client -> reqwest -> | |
| # rustls, and its build script assembles x86_64 code with NASM. | |
| # Installed explicitly rather than relied on from the runner image, so a | |
| # future image change cannot turn this job red for a reason unrelated to | |
| # the driver. | |
| - name: Install NASM (required to build aws-lc-sys) | |
| run: choco install nasm --no-progress -y | |
| - name: Add NASM to PATH | |
| run: echo "C:\Program Files\NASM" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| - name: Install Rust ${{ env.RUST_TOOLCHAIN_VERSION }} toolchain | |
| uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b | |
| with: | |
| toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} | |
| targets: x86_64-pc-windows-gnu | |
| # A separate cache key: the Linux job's artefacts are a different target | |
| # triple and sharing the key would thrash both. | |
| - name: Setup Rust Cache | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| key: windows-gnu-test | |
| # The GNU target's linker. The runner image ships MSYS2, but its mingw64 | |
| # bin directory is not on PATH by default. | |
| - name: Add MinGW to PATH | |
| run: echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append | |
| # `--target x86_64-pc-windows-gnu`, not the runner's default MSVC triple. | |
| # That is the target release.yaml builds and packaging/build-archives.sh | |
| # ships, and a suite passing against a toolchain nobody receives is only | |
| # evidence about that toolchain. odbc-sys links odbc32, which comes with | |
| # the Windows SDK already on the runner, so there is no equivalent of the | |
| # unixodbc-dev install the Linux jobs need. | |
| - name: Run unit tests | |
| run: cargo test --locked --target x86_64-pc-windows-gnu | |
| # Builds both shipping artifacts the way release.yaml does, and checks the two | |
| # properties that are invisible in a unit test run: that the DLL exports the | |
| # ODBC entry points, and that what each artifact links at load time still | |
| # matches packaging/sbom-native.json. The SBOM declares native dependencies by | |
| # hand, since no cargo metadata describes them, so nothing but this check keeps | |
| # the declaration true. | |
| release-artifacts: | |
| name: Release Artifacts | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| needs: [unit-tests] | |
| steps: | |
| - name: Install MinGW cross-compiler | |
| run: sudo apt-get update && sudo apt-get install -y gcc-mingw-w64-x86-64 unixodbc-dev | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| - name: Install Rust ${{ env.RUST_TOOLCHAIN_VERSION }} toolchain | |
| uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b | |
| with: | |
| toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} | |
| targets: x86_64-pc-windows-gnu | |
| - name: Setup Rust Cache | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| key: windows-gnu | |
| - name: Build Linux shared library | |
| run: cargo build --locked --release | |
| - name: Build Windows DLL | |
| run: cargo build --locked --target x86_64-pc-windows-gnu --release | |
| # Named symbols and a floor, not a printed count. The count alone used to | |
| # be piped into `xargs echo`, whose exit status is what the step was | |
| # graded on, so a DLL exporting nothing at all passed. The Driver Manager | |
| # resolves these by name, and a missing one is a load failure on a user's | |
| # machine that no unit test can see. | |
| # | |
| # ConfigDSNW is in the list because it is the only Windows-only export: | |
| # core builds it nowhere else, and it is what the ODBC Administrator's | |
| # Add.../Configure... buttons call. The Linux .so has 60 of these; the DLL | |
| # has 61. | |
| - name: Verify DLL exports | |
| run: | | |
| DLL=target/x86_64-pc-windows-gnu/release/stackable_odbc_trino.dll | |
| EXPORTS=$(x86_64-w64-mingw32-objdump -p "$DLL" \ | |
| | awk '/Export Address Table/,/Ordinal base/' \ | |
| | grep -oE '\b(SQL|Config)[A-Za-z]+\b' | sort -u) | |
| echo "$EXPORTS" | tr '\n' ' '; echo | |
| missing="" | |
| for sym in SQLAllocHandle SQLFreeHandle SQLDriverConnectW SQLConnectW \ | |
| SQLBrowseConnectW SQLDisconnect SQLPrepareW SQLExecute \ | |
| SQLExecDirectW SQLBindParameter SQLDescribeParam SQLFetch \ | |
| SQLGetData SQLNumResultCols SQLDescribeColW SQLGetInfoW \ | |
| SQLGetTypeInfoW SQLGetDiagRecW SQLTablesW SQLColumnsW \ | |
| SQLEndTran SQLCancel ConfigDSNW; do | |
| grep -qx "$sym" <<< "$EXPORTS" || missing="$missing $sym" | |
| done | |
| if [ -n "$missing" ]; then | |
| echo "::error::the DLL does not export:$missing" | |
| exit 1 | |
| fi | |
| # A floor as well as the named set, so a wholesale regression in | |
| # core's forward_ffi! is caught even if these particular names survive. | |
| count=$(echo "$EXPORTS" | grep -c .) | |
| if [ "$count" -lt 55 ]; then | |
| echo "::error::only $count ODBC symbols exported; expected at least 55" | |
| exit 1 | |
| fi | |
| echo "Trino DLL: $count ODBC symbols exported, all required names present" | |
| # Two different assertions behind one flag. For the .so it compares | |
| # DT_NEEDED against the sonames sbom-native.json declares, in both | |
| # directions. For the .dll it asserts the mingw runtime is still linked | |
| # statically: the release archive ships no runtime DLL, so an artifact | |
| # that imported one would fail to load on a user's machine. | |
| # | |
| # Only the release binaries are checked, and only here rather than in | |
| # release.yaml, because a pull request is where a dependency change can | |
| # still be reverted cheaply. | |
| - name: Verify declared native dependencies | |
| run: | | |
| ./packaging/sbom.sh --check-native target/release/libstackable_odbc_trino.so | |
| ./packaging/sbom.sh --check-native target/x86_64-pc-windows-gnu/release/stackable_odbc_trino.dll | |
| # The Trino compose stack, once per profile. Measured on ubuntu-24.04 before | |
| # this job was added: the coordinator idles at 871 MiB of 15.6 GiB with 13 GiB | |
| # left, and the whole job takes under five minutes, so the runner is nowhere | |
| # near its limits and no load reduction is configured. | |
| # | |
| # The matrix is over stack configurations, not over tests. suites/test_spooling.py | |
| # takes no required profile: with `spooling` active it drives the spooled | |
| # protocol, and without it asserts the fallback a coordinator with no spooling | |
| # manager produces. One profile therefore covers one branch of it, and running | |
| # both is what covers the pair. The legs are independent, hence fail-fast: | |
| # false, since knowing only that "one of them broke" would mean running it | |
| # again to learn which. | |
| # | |
| # `oauth` is not here. It needs a browser to complete an interactive login, | |
| # which is why suites/registry.py marks that suite for the Windows VM runner. | |
| integration-tests: | |
| name: Integration Tests (${{ matrix.profile || 'core' }}) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 30 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| profile: ["", "spooling"] | |
| env: | |
| # setup.sh reads this, and writes it into generated/stack.env, which | |
| # run-tests.sh sources. One value therefore decides both the stack that | |
| # starts and the suites that are considered active, so the two cannot | |
| # disagree about which profile is running. | |
| PROFILES: ${{ matrix.profile }} | |
| steps: | |
| # unixodbc is the Driver Manager the suites load the driver through, and | |
| # is a different package from the -dev libraries the build links against; | |
| # both are needed here. apache2-utils supplies htpasswd, which | |
| # gen-secrets.sh uses to write password.db. openssl, keytool and | |
| # docker compose come with the runner image. | |
| - name: Install host dependencies | |
| uses: awalsh128/cache-apt-pkgs-action@acb598e5ddbc6f68a970c5da0688d2f3a9f04d05 # v1.6.0 | |
| with: | |
| packages: unixodbc unixodbc-dev apache2-utils | |
| # A cache key, not a runner label, but it tracks the runner image so | |
| # that bumping the image invalidates the cached .deb files. | |
| version: ubuntu-24.04 | |
| - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 | |
| with: | |
| persist-credentials: false | |
| # uv runs the Python suites (`uv run --with pyodbc`) and is not on the | |
| # runner image. pipx is, and installing through it keeps this to one line | |
| # with no action to pin. | |
| - name: Install uv | |
| run: pipx install uv | |
| # Explicit, and before anything else that can fail, so a rate limit or a | |
| # missing tag is reported as itself rather than as a startup timeout | |
| # several minutes later. No service names: `pull` takes the active | |
| # profiles into account, so the spooling leg picks up MinIO (+89 MiB) and | |
| # the core leg does not pull it at all. | |
| # | |
| # The retry wraps the whole pull because the daemon's own retry budget is | |
| # per layer: once a single layer exhausts it, the pull fails and takes | |
| # every other image with it. Layers that did arrive stay in the local | |
| # content store, so a second attempt refetches only what is missing and | |
| # costs seconds. The spooling leg is the one that reaches quay.io for | |
| # MinIO, and that is where the dropped connections have been seen. | |
| - name: Pull the stack images | |
| # From the stack directory, as scripts/lib.sh's `compose` wrapper does. | |
| # Compose derives the project name from the compose file's directory, | |
| # and a mismatch here would leave the diagnostics step below querying a | |
| # project that holds no containers. | |
| working-directory: integration-tests/stack | |
| env: | |
| COMPOSE_PROFILES: ${{ matrix.profile }} | |
| run: | | |
| set -o pipefail | |
| log="$RUNNER_TEMP/pull.log" | |
| for attempt in 1 2 3; do | |
| if docker compose pull 2>&1 | tee "$log"; then | |
| exit 0 | |
| fi | |
| # Neither of these improves by asking again. A registry counts a | |
| # rate limit over hours, and a name it does not serve is a mistake | |
| # in the compose file, so both are reported as themselves instead | |
| # of spending two more attempts to reach the same answer. | |
| if grep -qiE 'toomanyrequests|rate limit' "$log"; then | |
| echo "::error::registry rate limit reached; the pull was not retried" | |
| exit 1 | |
| fi | |
| if grep -qiE 'manifest unknown|name unknown|repository does not exist|unauthorized|denied' "$log"; then | |
| echo "::error::a registry refused an image name or tag; the pull was not retried" | |
| exit 1 | |
| fi | |
| echo "::warning::pull attempt $attempt failed on a transient error" | |
| sleep $((attempt * 15)) | |
| done | |
| echo "::error::the stack images could not be pulled in three attempts" | |
| exit 1 | |
| - name: Install Rust ${{ env.RUST_TOOLCHAIN_VERSION }} toolchain | |
| uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b | |
| with: | |
| toolchain: ${{ env.RUST_TOOLCHAIN_VERSION }} | |
| # Shared across the matrix legs rather than keyed per profile: both build | |
| # the identical debug binary, and a per-leg key would double the cache | |
| # for no benefit and evict entries the other jobs here still want. | |
| - name: Setup Rust Cache | |
| uses: Swatinem/rust-cache@98c8021b550208e191a6a3145459bfc9fb29c4c0 # v2.8.0 | |
| with: | |
| key: integration | |
| # Ahead of setup.sh, which builds too. Doing it here separates a | |
| # compilation failure from a stack failure in the step list, and makes | |
| # setup.sh's own build the incremental no-op it is designed to be. | |
| - name: Build the driver | |
| run: cargo build --locked | |
| - name: Set up the Trino stack | |
| run: ./integration-tests/setup.sh | |
| - name: Run the integration suites | |
| # --skip-build because the build above left cargo nothing to do. | |
| # --skip-delete so the containers survive run-tests.sh's teardown trap | |
| # into the log capture below; the runner is discarded either way, and a | |
| # torn-down stack has no logs to explain why it failed. | |
| run: ./integration-tests/run-tests.sh --skip-build --skip-delete | |
| # A stack failure and a driver failure look alike in a red job, so | |
| # capture what separates them: the kernel's own OOM verdict, the memory | |
| # left, and the container logs. | |
| - name: Capture diagnostics on failure | |
| if: failure() | |
| working-directory: integration-tests/stack | |
| env: | |
| COMPOSE_PROFILES: ${{ matrix.profile }} | |
| run: | | |
| echo "=== memory ===" | |
| free -h | |
| echo "=== dmesg, OOM killer ===" | |
| sudo dmesg | grep -i -E 'out of memory|oom-kill' || echo "no OOM kill recorded" | |
| echo "=== container state ===" | |
| docker compose ps -a | |
| echo "=== logs ===" | |
| docker compose logs --tail=300 | |
| fuzz: | |
| name: Fuzz (ASAN smoke) | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| needs: [unit-tests] | |
| steps: | |
| # The fuzz binaries link this driver, which links libodbc through | |
| # odbc-sys, so the unixODBC dev libraries must be present to link them. | |
| - name: Install host dependencies | |
| uses: awalsh128/cache-apt-pkgs-action@553a35bb8ebd9fcabcb1c9451aa4c98e1b4ca8a9 # v1.6.3 | |
| with: | |
| packages: unixodbc-dev | |
| version: ubuntu-24.04 | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| persist-credentials: false | |
| # cargo-fuzz builds with libFuzzer + AddressSanitizer, which require a | |
| # nightly toolchain. The fuzz crate is its own Cargo workspace, so the | |
| # pinned stable root build never touches it. | |
| - name: Install nightly toolchain | |
| uses: dtolnay/rust-toolchain@2c7215f132e9ebf062739d9130488b56d53c060c # nightly | |
| with: | |
| toolchain: nightly | |
| # fuzz/ declares its own [workspace], so its build artifacts land in | |
| # fuzz/target, not the root target/. Without this the cache stores an | |
| # empty directory and every run rebuilds nightly + ASAN from scratch. | |
| - name: Setup Rust Cache | |
| uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| key: fuzz | |
| workspaces: "fuzz -> target" | |
| - name: Install cargo-fuzz | |
| uses: taiki-e/install-action@1beb33eee6d086258184383af9a538940be190ed # v2.85.6 | |
| with: | |
| tool: cargo-fuzz | |
| # A short smoke run per target: long enough to catch a regression that | |
| # reintroduces a shallow crash, short enough for per-PR CI. Finding a new | |
| # defect is the job of a longer run, not of this gate. | |
| # | |
| # --target is pinned to the gnu triple explicitly: newer cargo-fuzz | |
| # defaults to x86_64-unknown-linux-musl, whose statically linked libc is | |
| # incompatible with AddressSanitizer ("sanitizer is incompatible with | |
| # statically linked libc"). gnu uses a dynamic libc and ships with the | |
| # nightly toolchain. | |
| - name: Fuzz json_value | |
| run: cargo +nightly fuzz run json_value --target x86_64-unknown-linux-gnu -- -max_total_time=30 | |
| - name: Fuzz type_name | |
| run: cargo +nightly fuzz run type_name --target x86_64-unknown-linux-gnu -- -max_total_time=30 | |
| - name: Fuzz escape | |
| run: cargo +nightly fuzz run escape --target x86_64-unknown-linux-gnu -- -max_total_time=30 | |
| - name: Fuzz connect_params | |
| run: cargo +nightly fuzz run connect_params --target x86_64-unknown-linux-gnu -- -max_total_time=30 | |
| # Single required check for branch protection rules. | |
| finished: | |
| name: Finished Build and Test | |
| if: always() | |
| needs: | |
| - pre-commit | |
| - unit-tests | |
| - unit-tests-windows | |
| - release-artifacts | |
| - integration-tests | |
| - fuzz | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| steps: | |
| # Derived from needs.* rather than a hand-written list of job names: a job | |
| # added to `needs` above but forgotten here would otherwise be silently | |
| # non-blocking, which is exactly how the lint gate came to sit in a | |
| # workflow this check never observed. | |
| - name: Check job results | |
| env: | |
| RESULTS: ${{ join(needs.*.result, ' ') }} | |
| run: | | |
| for result in $RESULTS; do | |
| if [[ "$result" != "success" ]]; then | |
| echo "One or more jobs did not succeed: $RESULTS" | |
| exit 1 | |
| fi | |
| done | |
| echo "All jobs passed: $RESULTS" |