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
name: CI
on:
push:
branches:
pull_request:
branches:
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
# Every job below runs a `just` recipe rather than an inline cargo command.
# The justfile is the single source of truth for what the checks are, so
# `just ci` locally runs exactly what CI runs and the two cannot drift.
# Feature strings in particular live in one place (justfile variables) instead
# of being duplicated across YAML.
#
# `ring` and `rustls-aws-lc-rs` are additive: when both are enabled,
# `rustls-aws-lc-rs` wins (see `auth::tls::resolve_crypto_provider`), so
# `--all-features` is valid and needs no hand-maintained exclusion list.
jobs:
fmt:
name: Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- uses: extractions/setup-just@v3
- run: just fmt-check
clippy:
name: Clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- run: just clippy
check:
name: Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- run: just check
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- run: just test
# `ring` is the DEFAULT backend, so it is what most downstream users compile.
# It must be *tested*, not merely type-checked: the
# `cfg(not(feature = "rustls-aws-lc-rs"))` arms in `auth/tls.rs` and
# `schema_registry/http.rs` only run here.
test-ring:
name: Test (ring backend)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- run: just test-ring
# Guards the minimum viable configuration. A missing crypto backend used to
# fail deep inside rustls; the `compile_error!` in lib.rs plus this job keep
# that diagnostic honest, and the second command pins feature additivity.
minimal-features:
name: Minimal features
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- run: just minimal-features
# A network client's socket options, dual-stack connection racing and
# filesystem handling are platform-sensitive. Without these jobs non-Linux
# targets were never compiled at all.
cross-platform:
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- run: just test-cross-platform
protocol-parity:
name: Protocol version parity
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: extractions/setup-just@v3
# Reads the vendored `xtask/kafka_protocol_snapshot.json`, so this job
# needs no network and cannot flake on a GitHub API rate limit. Refresh
# the snapshot deliberately with `just refresh-protocol-snapshot <tag>`.
- run: just protocol-parity
secret-debug:
name: No secrets in Debug
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: extractions/setup-just@v3
- run: just secret-debug
version-check:
name: Version consistency
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: extractions/setup-just@v3
- run: just version-check
test-reachability:
name: No tests that cannot fail
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: extractions/setup-just@v3
- run: just test-reachability
config-reachability:
name: No config field that cannot be set
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: extractions/setup-just@v3
- run: just config-reachability
protocol-reachability:
name: No decoded field that is never read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: extractions/setup-just@v3
- run: just protocol-reachability
doc:
name: Documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- run: just doc
# The gate behind every ```rust,compile block in README.md and
# site/content/docs/. It was in `just ci` — and therefore in the release
# recipe — for its whole life without ever running in a workflow, so a pull
# request could break every documented sample and stay green. That is the
# same shape as the SASL suite, which was runnable locally long before
# anyone noticed it had never run in CI.
#
# `doc_api.py` checks that names resolve; it cannot check that a call has
# the right shape. This compiles the snippets instead.
# Asserts that every recipe in `just ci` has a job here, that the
# `ci-success` aggregator needs every job, and that it carries
# `if: always()`. Closes the class that `docs-test` and `integration-sasl`
# were both instances of.
ci-job-parity:
name: CI job parity
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: extractions/setup-just@v3
- run: just ci-job-parity
docs-test:
name: Documentation snippets
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- run: just docs-test
# Classifies API changes against the last published release. Pre-1.0 this
# reports rather than blocks (D1 allows a minor bump to break), but the
# output is what the CHANGELOG's Breaking section owes the reader — and
# until now nothing checked that list was complete.
semver:
name: SemVer classification
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
# A prebuilt binary rather than `cargo install`, which takes minutes.
# The check itself runs through the justfile so CI and a developer's
# machine cannot drift (D22).
- uses: taiki-e/install-action@v2
with:
tool: cargo-semver-checks
- run: just semver-check
supply-chain:
name: Supply chain (cargo-deny)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check advisories bans licenses sources
integration:
name: Integration Tests (Kafka ${{ matrix.kafka-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
# apache/kafka-native (GraalVM) is fast but segfaults on getpwuid in
# some CI environments; use it only where it is known to be stable.
- kafka-version: "3.9.0"
kafka-image: "apache/kafka-native"
# Kafka 4.x native images consistently segfault on GitHub Actions
# (GraalVM Pwd.getpwuid bug); use the JVM image instead.
#
# 3.9 is the supported floor and 4.3 the protocol-parity target;
# every minor between them runs so a version-specific regression
# names the exact broker line that broke.
- kafka-version: "4.0.0"
kafka-image: "apache/kafka"
- kafka-version: "4.1.0"
kafka-image: "apache/kafka"
- kafka-version: "4.2.0"
kafka-image: "apache/kafka"
- kafka-version: "4.3.0"
kafka-image: "apache/kafka"
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- name: Pre-pull Kafka image
run: |
for i in 1 2 3; do
docker pull ${{ matrix.kafka-image }}:${{ matrix.kafka-version }} && break
echo "Retry $i failed, waiting..."
sleep 10
done
- run: just integration
timeout-minutes: 45
env:
KAFKA_VERSION: ${{ matrix.kafka-version }}
KAFKA_IMAGE: ${{ matrix.kafka-image }}
# The SASL suite existed before this job did — it was runnable locally via
# `just integration-sasl` but never ran in CI, so a SASL handshake
# regression could only be caught by hand. Its harness uses the Confluent
# image (the apache images do not ship the JAAS entrypoint hooks it needs).
integration-sasl:
name: Integration Tests (SASL)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- name: Pre-pull Kafka image
run: |
for i in 1 2 3; do
docker pull confluentinc/cp-kafka:7.5.0 && break
echo "Retry $i failed, waiting..."
sleep 10
done
- run: just integration-sasl
timeout-minutes: 45
# Redpanda speaks the Kafka wire protocol; krafka negotiates every API
# version, so this suite pins the compatibility that negotiation is supposed
# to buy — including the KIP-890 TV1 fallback (Redpanda has no server-side
# TV2). `latest` rather than a pinned tag: the claim under test is "current
# Redpanda works", and a pin would quietly narrow it to "one old Redpanda
# once worked".
integration-redpanda:
name: Integration Tests (Redpanda)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- name: Pre-pull Redpanda image
run: |
for i in 1 2 3; do
docker pull redpandadata/redpanda:latest && break
echo "Retry $i failed, waiting..."
sleep 10
done
- run: just integration-redpanda
timeout-minutes: 30
msrv:
name: MSRV (1.95)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@master
with:
toolchain: "1.95"
- uses: Swatinem/rust-cache@v2
# Not `just msrv`: that recipe shells out to `rustup run 1.95` so a
# developer can check the MSRV without switching their default toolchain.
# Here the job's toolchain already *is* 1.95, so the plain command is
# both simpler and a genuine check that the pinned toolchain builds.
- run: cargo check
# ── The single required status check ────────────────────────────────────────
#
# Branch protection names THIS job and nothing else. Without it, every job
# above has to be listed by hand in the repository settings, and a newly
# added job is not required by default — it runs, it can fail, and the merge
# button stays green until somebody remembers to go and add it.
#
# Two GitHub behaviours make the obvious implementation wrong, and both are
# guarded against here:
#
# 1. A required check whose job never runs stays *pending* forever, so the
# pull request can never merge. `if: always()` makes this job run even
# when a dependency failed or was skipped.
# 2. GitHub reads a *skipped* required check as **success**. So this job
# must not simply skip on failure — it has to run and then fail
# explicitly, which is what the `contains(needs.*.result, ...)` test
# below does. Checking `success()` would not be enough either, because
# a skipped dependency does not make `success()` false.
#
# `needs` is exhaustive on purpose. `just ci-job-parity` asserts that this
# list matches the jobs actually defined in this file, so adding a job
# without wiring it here fails the build rather than silently going
# unrequired.
ci-success:
name: CI
if: always()
runs-on: ubuntu-latest
needs:
- fmt
- clippy
- check
- test
- test-ring
- minimal-features
- cross-platform
- protocol-parity
- secret-debug
- version-check
- test-reachability
- config-reachability
- protocol-reachability
- ci-job-parity
- doc
- docs-test
- semver
- supply-chain
- integration
- integration-sasl
- integration-redpanda
- msrv
steps:
- name: Verify every job succeeded
run: |
echo "Results: ${{ join(needs.*.result, ', ') }}"
if ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') || contains(needs.*.result, 'skipped') }}; then
echo "::error::At least one CI job did not succeed."
exit 1
fi
echo "All CI jobs succeeded."