Repository navigation
256 lines (245 loc) · 10.3 KB
/
Copy pathci.yml
File metadata and controls
256 lines (245 loc) · 10.3 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
# The test suites, on each push to main and each pull request.
#
# static shellcheck, actionlint, py_compile and the policy checks
# (tests/static)
# unit the wrapper and the scripts under three shells and three
# awks, one job each: dash and mawk on the runner, bash and
# gawk in Arch, busybox in Alpine (tests/unit)
# integration one job per file in tests/integration, found when the run
# starts, so a new file gets its own job with no edit here
# worker the Cloudflare Worker's tests in the node image (tests/worker)
# debloat scripts/debloat-runner on a real runner, measured
#
# ⛔ No job installs a package from a distro mirror. scripts/install-test-tools
# downloads pinned static binaries and checks each sha256. The containers
# mount that directory read-only, last on PATH: a tool fills a gap, and the
# image's own tools stay the ones under test.
#
# Push runs only on main, so a pull request from a branch here runs once, not
# twice. The status branch holds a generated site and never runs this.
name: CI
on:
push:
branches: [main]
pull_request:
branches-ignore: [status]
workflow_dispatch:
permissions: {}
# One group per pull request: a newer push to it cancels the run in progress.
# Any other run has a group of its own (its run id). A group holds one pending
# run, and a newer run cancels it, so a shared group for main would leave a
# commit between two quick pushes with no verdict. No job writes shared state,
# so runs on main need not wait for each other.
concurrency:
group: ci-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || github.run_id }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
defaults:
run:
shell: bash
jobs:
static:
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Static suite
env:
# Here each of these is present, so a test that needs one fails
# rather than skips when it is not.
TESTS_REQUIRE_TOOLS: shellcheck actionlint jq python3
run: |
scripts/install-test-tools "$RUNNER_TEMP/bin" shellcheck actionlint jq
python3 --version
# The runner's own test first, without the runner: a run.sh that
# hides failures would hide this one too.
REPO_ROOT="$PWD" bash tests/static/72-test-runner.sh
PATH="$RUNNER_TEMP/bin:$PATH" tests/run.sh static
unit:
name: unit (${{ matrix.leg }})
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- leg: dash-mawk
image: ''
require: jq curl python3
- leg: bash-gawk
# tag: base
image: docker.io/library/archlinux:base@sha256:3c64b538588416593f69a1aebd2f2ec0f2541b29159f32556fa7def2167147a0
require: jq curl
- leg: busybox
# tag: 3.24
image: docker.io/library/alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6
require: jq curl
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Test tools
run: scripts/install-test-tools "$RUNNER_TEMP/tools" jq curl bash
- name: Unit suite
env:
LEG: ${{ matrix.leg }}
IMAGE: ${{ matrix.image }}
TESTS_REQUIRE_TOOLS: ${{ matrix.require }}
run: |
# The shell, the awk and the tools each leg runs, asserted, so a
# change in an image shows here and not as a passing run of the
# wrong thing.
cat > "$RUNNER_TEMP/leg.sh" << 'EOF'
set -eu
PATH="$PATH:$TOOLS"
export PATH
sh_is="$(readlink -f /bin/sh)"
awk_is="$(readlink -f "$(command -v awk)")"
echo "$LEG: sh $sh_is, awk $awk_is, bash $(command -v bash), curl $(command -v curl), jq $(command -v jq)"
case "$LEG:$sh_is:$awk_is" in
dash-mawk:/usr/bin/dash:/usr/bin/mawk) ;;
bash-gawk:/usr/bin/bash:/usr/bin/gawk*) ;;
busybox:/bin/busybox:/bin/busybox) ;;
*)
echo "::error::$LEG runs sh $sh_is and awk $awk_is, which is not what this leg tests"
exit 1
;;
esac
exec bash tests/run.sh unit
EOF
if [ "$LEG" = dash-mawk ]; then
# Ubuntu's awk is gawk once gawk is installed; mawk is still
# there, and goes first on PATH here.
mkdir -p "$RUNNER_TEMP/mawk"
ln -sf /usr/bin/mawk "$RUNNER_TEMP/mawk/awk"
PATH="$RUNNER_TEMP/mawk:$PATH" TOOLS="$RUNNER_TEMP/tools" sh "$RUNNER_TEMP/leg.sh"
else
docker run --rm -v "$PWD:/work:ro" -v "$RUNNER_TEMP/tools:/tools:ro" \
-v "$RUNNER_TEMP/leg.sh:/leg.sh:ro" -w /work \
-e LEG -e TESTS_REQUIRE_TOOLS -e TOOLS=/tools "$IMAGE" sh /leg.sh
fi
# The matrix of the next job, from tests/run.sh --list.
integration-files:
runs-on: ubuntu-24.04
timeout-minutes: 5
permissions:
contents: read
outputs:
files: ${{ steps.list.outputs.files }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: List the integration test files
id: list
run: |
names="$(tests/run.sh --list integration)"
json=""
while IFS= read -r f; do
[ -n "$f" ] || continue
case "$f" in
*[!A-Za-z0-9._-]*)
echo "::error::tests/integration/$f: a test file name is letters, digits, '.', '_' and '-' only"
exit 1
;;
esac
json="${json:+$json,}\"$f\""
done <<< "$names"
if [ -z "$json" ]; then
echo "::error::tests/run.sh --list integration found no test file"
exit 1
fi
echo "files=[$json]" | tee -a "$GITHUB_OUTPUT"
integration:
needs: integration-files
runs-on: ubuntu-24.04
timeout-minutes: 45
permissions:
contents: read
strategy:
fail-fast: false
matrix:
file: ${{ fromJSON(needs.integration-files.outputs.files) }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Integration test
env:
CONTAINER_RUNTIME: docker
TEST_FILE: ${{ matrix.file }}
run: tests/run.sh integration "$TEST_FILE"
worker:
runs-on: ubuntu-24.04
timeout-minutes: 10
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Test tools
run: scripts/install-test-tools "$RUNNER_TEMP/tools" jq curl bash
- name: Worker suite
env:
# The node image of .github/workflows/worker.yml. Renovate moves
# the two in one pull request (group "node image").
# tag: 24-alpine
IMAGE: docker.io/library/node:24-alpine@sha256:44fce696f38ba8c987c4db89954051000bbd05ffd8d8659ba15ed4a8602e937f
run: |
docker run --rm -v "$PWD:/work:ro" -v "$RUNNER_TEMP/tools:/tools:ro" -w /work \
-e TESTS_REQUIRE_TOOLS='node bash' "$IMAGE" \
sh -c 'PATH="$PATH:/tools"; export PATH; node --version; exec bash tests/run.sh worker'
# Measured, not judged: a runner image that no longer carries these
# directories frees less, and that is worth a warning, not a red run.
# debloat-runner failing outright still fails the job.
debloat:
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Debloat, forced, and measured
run: |
before="$(df --output=avail -B1 "$RUNNER_TEMP" | awk 'NR == 2 { print $1 }')"
scripts/debloat-runner --dir "$RUNNER_TEMP"
after="$(df --output=avail -B1 "$RUNNER_TEMP" | awk 'NR == 2 { print $1 }')"
freed=$((after - before))
echo "freed $((freed / 1048576)) MiB on $ImageOS $ImageVersion"
echo "debloat-runner freed $((freed / 1048576)) MiB on $ImageOS $ImageVersion" >> "$GITHUB_STEP_SUMMARY"
if [ "$freed" -lt 1073741824 ]; then
echo "::warning::debloat-runner freed $((freed / 1048576)) MiB, less than 1 GiB, on $ImageOS $ImageVersion. The runner image changed under it: compare its TARGETS with du -sh of each on this image."
fi
# The trust root of every target outside x86_64: the keyring files verify
# takes from each keyring_image. They must equal targets/keyrings.sha256.
# So Renovate may merge a new keyring_image digest on its own while the
# keys stay the same, and a change of keys waits for a person.
keyrings:
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: The keyring files of the keyring images are the reviewed ones
env:
CONTAINER_RUNTIME: docker
run: |
scripts/keyring-files "$RUNNER_TEMP/keyrings.tsv"
grep -v '^#' targets/keyrings.sha256 > "$RUNNER_TEMP/reviewed.tsv"
if ! diff -u "$RUNNER_TEMP/reviewed.tsv" "$RUNNER_TEMP/keyrings.tsv" > "$RUNNER_TEMP/keyrings.diff"; then
cat "$RUNNER_TEMP/keyrings.diff"
echo "::error::the keyring files of a keyring_image changed: the keys verify trusts are not the reviewed ones. Review the change, then write the new lines into targets/keyrings.sha256 (scripts/keyring-files OUT prints them)."
exit 1
fi
echo "the keyring files of every keyring_image are the reviewed ones: $(wc -l < "$RUNNER_TEMP/keyrings.tsv") file(s)"