Skip to content

chore(release): version 0.1.2 #46

chore(release): version 0.1.2

chore(release): version 0.1.2 #46

Workflow file for this run

---
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"