Repository navigation
485 lines (430 loc) · 21.1 KB
/
Copy pathbuild.yaml
File metadata and controls
485 lines (430 loc) · 21.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
---
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 pulls the most, MinIO on top of the
# core images, so it is the one most exposed to a dropped connection.
- 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"