Repository navigation
403 lines (362 loc) · 16.6 KB
/
Copy pathtests-golang.yml
File metadata and controls
403 lines (362 loc) · 16.6 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
name: Tests (Go)
# The Go module (languages/golang) and the WASI guests it embeds, ported from
# cipherstash-suite's `test-wasi.yml`. The guests are detached Cargo
# workspaces and the Go module is its own module, so nothing else in CI
# compiles, tests or links them: this workflow is their only gate.
#
# wasi-check the stack crates build for wasm32-wasip1 with no JS-host or
# native-HTTP dependencies, the no-http shape passes its tests
# and docs, both guests pass lint and tests and are built with
# their import surfaces checked (the stack-encrypt guest four
# times: the real build, the build with the EQL types, and the
# deterministic-kms test build of each), their
# sha256 is recorded, `go:test` runs against them, and
# `go generate` leaves the tree unchanged.
# go-lint golangci-lint, Linux only.
# go-binding-cross
# the same Go tests on macOS and Windows, against the guests
# Linux built, after checking their sha256.
# live the Go live tests and the stack-encrypt examples, against real
# ZeroKMS with the repository's CS_* credentials.
#
# Every job except go-binding-cross runs mise at the repository root. The
# macOS and Windows jobs install only Go, reading the version from the root
# mise.toml, so they need no Rust.
on:
pull_request:
paths:
- packages/stack-auth/**
- packages/stack-profile/**
- packages/stack-kms/**
- packages/stack-encrypt/**
- packages/stack-encrypt-derive/**
- packages/stack-guest-abi/**
# The eql guest build links eql-bindings.
- packages/eql/crates/eql-bindings/**
- packages/eql/crates/eql-domains/**
- languages/golang/**
- scripts/check-wasm-imports.py
- scripts/go-binding-test.sh
- Cargo.toml
- Cargo.lock
- .cargo/**
- mise.toml
- mise.test.toml
- .github/actions/require-cs-secrets/**
- .github/workflows/tests-golang.yml
# Keep these excludes last so docs-only changes are skipped.
- "!**.md"
- "!**.example"
push:
branches: [main]
paths:
- packages/stack-auth/**
- packages/stack-profile/**
- packages/stack-kms/**
- packages/stack-encrypt/**
- packages/stack-encrypt-derive/**
- packages/stack-guest-abi/**
# The eql guest build links eql-bindings.
- packages/eql/crates/eql-bindings/**
- packages/eql/crates/eql-domains/**
- languages/golang/**
- scripts/check-wasm-imports.py
- scripts/go-binding-test.sh
- Cargo.toml
- Cargo.lock
- .cargo/**
- mise.toml
- mise.test.toml
- .github/actions/require-cs-secrets/**
- .github/workflows/tests-golang.yml
- "!**.md"
- "!**.example"
workflow_dispatch: {}
defaults:
run:
shell: bash
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
RUSTFLAGS: "-D warnings"
RUST_BACKTRACE: full
CARGO_TERM_COLOR: always
CARGO_NET_GIT_FETCH_WITH_CLI: true
jobs:
wasi-check:
name: WASI check, guests and Go (Linux)
runs-on: blacksmith-16vcpu-ubuntu-2204
timeout-minutes: 60
env:
# wasm:guest:test and wasm:auth-guest:test run nextest, which is pinned
# in mise.test.toml. In the test environment mise-action installs (and
# caches) the whole test toolset, which `mise run` would otherwise
# install uncached.
MISE_ENV: test
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4
with:
version: 2026.4.0
install: true
working_directory: .
cache: true
# Must name runs-on's image; see scripts/__tests__/workflow-mise-cache-image.test.mjs
cache_key_prefix: mise-v1-ubuntu-2204
# mise caches its rust install as a symlink into ~/.rustup, which the
# cache does not hold. On a runner image that already carries a 1.94.1
# toolchain, a cache hit makes mise skip the components mise.toml
# declares, and the lint steps below fail. A no-op when they are there.
# Held by scripts/__tests__/crates-ci.test.mjs.
- name: Add rustfmt and clippy to the pinned toolchain
run: rustup component add rustfmt clippy
# The root workspace and both guest workspaces, each with its own
# target/.
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: |
.
languages/golang/encrypt/guest
languages/golang/auth/guest
# The HTTP-free core compiles for wasm32-wasip1 with no JS-host backend
# and no native HTTP/TLS stack in its graph: the invariant the wazero
# binding is built on. See docs/wasm-analysis.md Layer 6.
- name: WASI core check
run: mise run wasm:wasi-check
# The same no-http shape on the host: unit tests, doctests and rustdoc
# with default features off. The compile gate above cannot see doc
# examples or intra-doc links.
- name: No-http tests and docs
run: mise run wasm:no-http-test
- name: stack-encrypt guest lint and tests
run: mise run wasm:guest:test
# Builds the release module and checks its host-import surface is
# exactly WASI (minus ambient filesystem and sockets) plus the two
# `cipherstash_transport` functions.
- name: stack-encrypt guest release build and import-surface gate
run: mise run wasm:guest:build
# The deterministic-kms TEST build, under encrypt/testdata where no
# build or embed sees it: the Go tests open the record fixture Rust
# sealed through it, and run round trips with no ZeroKMS. The tests
# skip when it is absent, so a build that forgets this step would pass
# with less coverage, which is why the job builds it unconditionally.
- name: stack-encrypt guest deterministic test build
run: mise run wasm:guest:build:deterministic
# The build with the EQL types (ADR-0007, amended 2026-10-06), which
# package encrypt/eql embeds and registers on import, and its
# deterministic test build, which the hermetic tests of a TextEq field
# load. Same import-surface gate. The build task prints both builds'
# sizes: the plan asks for that measurement before the SDK settles on
# one build or two.
- name: stack-encrypt guest eql build and import-surface gate
run: mise run wasm:guest:build:eql
- name: stack-encrypt guest eql deterministic test build
run: mise run wasm:guest:build:eql:deterministic
- name: Credential guest lint and tests
run: mise run wasm:auth-guest:test
- name: Credential guest release build and import-surface gate
run: mise run wasm:auth-guest:build
# The modules checked above are what every platform tests. Their
# checksums travel with them, so the other jobs can prove they got the
# same bytes rather than a stale or rebuilt guest.
- name: Record the guests' checksums
run: |
for guest in encrypt/wasm/stack_encrypt_guest.wasm encrypt/testdata/stack_encrypt_guest_deterministic.wasm encrypt/eql/wasm/stack_encrypt_guest_eql.wasm encrypt/testdata/stack_encrypt_guest_eql_deterministic.wasm auth/wasm/stack_auth_guest.wasm; do
(cd languages/golang && openssl dgst -sha256 "$guest" | awk '{print $NF}' > "$guest.sha256" && echo "$guest sha256 $(cat "$guest.sha256")")
done
- name: Hand the guests to the other jobs
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: wasm-guests
path: |
languages/golang/encrypt/wasm/stack_encrypt_guest.wasm
languages/golang/encrypt/wasm/stack_encrypt_guest.wasm.sha256
languages/golang/encrypt/testdata/stack_encrypt_guest_deterministic.wasm
languages/golang/encrypt/testdata/stack_encrypt_guest_deterministic.wasm.sha256
languages/golang/encrypt/eql/wasm/stack_encrypt_guest_eql.wasm
languages/golang/encrypt/eql/wasm/stack_encrypt_guest_eql.wasm.sha256
languages/golang/encrypt/testdata/stack_encrypt_guest_eql_deterministic.wasm
languages/golang/encrypt/testdata/stack_encrypt_guest_eql_deterministic.wasm.sha256
languages/golang/auth/wasm/stack_auth_guest.wasm
languages/golang/auth/wasm/stack_auth_guest.wasm.sha256
if-no-files-found: error
retention-days: 1
# Format, vet and hermetic tests on amd64 and 386. The guest's memory
# lock is best effort, so its test skips where RLIMIT_MEMLOCK refuses
# it; CI raises the limit and sets STACK_ENCRYPT_TESTS_REQUIRE_LOCK so
# the skip is an error here.
- name: Go binding
env:
STACK_ENCRYPT_TESTS_REQUIRE_LOCK: "1"
run: |
ulimit -l "$(ulimit -H -l)"
echo "RLIMIT_MEMLOCK: $(ulimit -l) KiB"
mise run go:test
# What is encrypted is fixed before the program ships: every generated
# file is committed, and this fails when `go generate` would change
# one. It runs the real stashgen against the guest just built, so it
# also proves the generator and the engine agree on the module's own
# examples. `git diff` sees only tracked files, so the status check
# catches a generated file that was never committed.
- name: Generated code is committed
working-directory: languages/golang
run: |
CGO_ENABLED=0 go generate ./...
git diff --exit-code -- .
untracked="$(git status --porcelain -- .)"
if [ -n "$untracked" ]; then
echo "::error::go generate wrote files that are not committed:"
echo "$untracked"
exit 1
fi
# Linux only: macOS and Windows would report the same findings. Needs no
# guests: the packages embed a directory and compile without them.
go-lint:
name: Go lint
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 15
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4
with:
version: 2026.4.0
install: true
working_directory: .
install_args: go golangci-lint
cache: true
# Must name runs-on's image; see scripts/__tests__/workflow-mise-cache-image.test.mjs
cache_key_prefix: mise-v1-ubuntu-2404
- name: golangci-lint
run: mise run go:lint
# The Go binding on macOS and Windows, against the guests Linux built. The
# credential guest's refresh lock has Unix and Windows implementations, and
# its device refresh tests have no skip. Go plus the artifact is the whole
# toolchain here.
go-binding-cross:
name: Go binding (${{ matrix.os }})
needs: wasi-check
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
# Git for Windows defaults to CRLF on checkout, and gofmt would then
# report every file. Set this before checkout.
- name: Keep LF line endings
run: git config --global core.autocrlf false
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
# One source of truth for the Go version: the root mise pin the Linux
# job runs under. If the pin changes shape, fail here by name rather
# than hand setup-go an empty version.
- name: Go version from mise.toml
id: go
run: |
version=$(sed -n 's/^go = "\(.*\)"/\1/p' mise.toml)
if [ -z "$version" ]; then
echo 'no go = "..." line in mise.toml; the Go version pin has changed shape' >&2
exit 1
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
- uses: actions/setup-go@v5
with:
go-version: ${{ steps.go.outputs.version }}
cache-dependency-path: languages/golang/go.sum
# The artifact keeps its paths relative to their common root
# (languages/golang), so both guests land where the packages embed them.
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: wasm-guests
path: languages/golang
- name: The guests are the ones Linux built and checked
run: |
cd languages/golang
for guest in encrypt/wasm/stack_encrypt_guest.wasm encrypt/testdata/stack_encrypt_guest_deterministic.wasm encrypt/eql/wasm/stack_encrypt_guest_eql.wasm encrypt/testdata/stack_encrypt_guest_eql_deterministic.wasm auth/wasm/stack_auth_guest.wasm; do
want=$(cat "$guest.sha256")
got=$(openssl dgst -sha256 "$guest" | awk '{print $NF}')
if [ "$want" != "$got" ]; then
echo "$guest checksum mismatch: artifact says $want, file is $got" >&2
exit 1
fi
echo "$guest sha256 $got"
done
- name: Go binding
run: scripts/go-binding-test.sh languages/golang
# Round trips through real ZeroKMS. The Go live tests skip without their
# STACK_ENCRYPT_TEST_* variables, so the secrets pre-flight runs first and
# turns a missing credential into a failure rather than a silent skip. The
# stack-encrypt examples read CS_* through `StackCipher::new()`; tests-crates
# builds them, and this job runs them.
live:
name: Go live tests and stack-encrypt examples
needs: wasi-check
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 30
env:
CS_WORKSPACE_CRN: ${{ vars.CS_WORKSPACE_CRN }}
CS_CLIENT_ID: ${{ vars.CS_CLIENT_ID }}
CS_CLIENT_KEY: ${{ secrets.CS_CLIENT_KEY }}
CS_CLIENT_ACCESS_KEY: ${{ secrets.CS_CLIENT_ACCESS_KEY }}
STACK_ENCRYPT_TEST_WORKSPACE_CRN: ${{ vars.CS_WORKSPACE_CRN }}
STACK_ENCRYPT_TEST_CLIENT_ID: ${{ vars.CS_CLIENT_ID }}
STACK_ENCRYPT_TEST_CLIENT_KEY: ${{ secrets.CS_CLIENT_KEY }}
STACK_ENCRYPT_TEST_CLIENT_ACCESS_KEY: ${{ secrets.CS_CLIENT_ACCESS_KEY }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
# Fast pre-flight: fail in seconds if a secret was rotated or cleared.
# Ordering is asserted by scripts/__tests__/ffi-binding-step-order.test.mjs.
- uses: ./.github/actions/require-cs-secrets
with:
workspace-crn: ${{ vars.CS_WORKSPACE_CRN }}
client-id: ${{ vars.CS_CLIENT_ID }}
client-key: ${{ secrets.CS_CLIENT_KEY }}
client-access-key: ${{ secrets.CS_CLIENT_ACCESS_KEY }}
- uses: jdx/mise-action@1648a7812b9aeae629881980618f079932869151 # v4
with:
version: 2026.4.0
install: true
working_directory: .
install_args: rust go
cache: true
# Must name runs-on's image; see scripts/__tests__/workflow-mise-cache-image.test.mjs
cache_key_prefix: mise-v1-ubuntu-2404
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: wasm-guests
path: languages/golang
- name: The guests are the ones the wasi-check job built and checked
run: |
cd languages/golang
for guest in encrypt/wasm/stack_encrypt_guest.wasm encrypt/testdata/stack_encrypt_guest_deterministic.wasm encrypt/eql/wasm/stack_encrypt_guest_eql.wasm encrypt/testdata/stack_encrypt_guest_eql_deterministic.wasm auth/wasm/stack_auth_guest.wasm; do
want=$(cat "$guest.sha256")
got=$(openssl dgst -sha256 "$guest" | awk '{print $NF}')
if [ "$want" != "$got" ]; then
echo "$guest checksum mismatch: artifact says $want, file is $got" >&2
exit 1
fi
echo "$guest sha256 $got"
done
# The whole package, not a `-run` filter: live tests are the ones that
# call `liveClient`, and not all of them are named `TestLive*`.
- name: Go live tests
working-directory: languages/golang
run: CGO_ENABLED=0 go test -v ./encrypt/...
- name: stack-encrypt examples
run: |
for example in encrypted_record mixed_user search_terms zerokms_auth; do
echo "==> $example"
cargo run --locked -p stack-encrypt --example "$example"
done