-
Notifications
You must be signed in to change notification settings - Fork 4
282 lines (249 loc) · 11.2 KB
/
build.yml
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
name: Build
on:
workflow_dispatch:
inputs:
buildId:
required: true
type: string
bashbrewArch:
required: true
type: choice
options:
- amd64
- i386
- windows-amd64
firstTag: # informational only, because "run-name" can't be set to a useful value otherwise
type: string
windowsVersion:
type: choice
options:
- '' # without this, it's technically "required" 🙃
- 2022
- 2019
run-name: '${{ inputs.bashbrewArch }}: ${{ inputs.firstTag }} (${{ inputs.buildId }})'
permissions:
contents: read
actions: write # for https://github.com/andymckay/cancel-action (see usage below)
id-token: write # for AWS KMS signing (see usage below)
concurrency:
group: ${{ github.event.inputs.buildId }}
cancel-in-progress: false
defaults:
run:
shell: 'bash -Eeuo pipefail -x {0}'
env:
BUILD_ID: ${{ inputs.buildId }}
BASHBREW_ARCH: ${{ inputs.bashbrewArch }}
jobs:
build:
name: Build ${{ inputs.buildId }}
runs-on: ${{ inputs.bashbrewArch == 'windows-amd64' && format('windows-{0}', inputs.windowsVersion) || 'ubuntu-latest' }}
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
# TODO on Linux, install Tianon's Docker builds (switch off "ubuntu-latest" to pin to something closer to something we publish Debian builds for OR just run Docker-in-Docker and use GITHUB_ENV to set DOCKER_HOST to a suitable value)
- uses: ./.doi/.github/workflows/.bashbrew
with:
# avoid building because we want to skip the build and download a release instead (which will be way faster)
build: none # this will set BASHBREW_VERSION for us
# TODO improve the bashbrew action to download a release binary instead of building from source ("build: download", perhaps?)
- name: Tools
run: |
mkdir .gha-bin
echo "$PWD/.gha-bin" >> "$GITHUB_PATH"
ext=''
if [ "$BASHBREW_ARCH" = 'windows-amd64' ]; then # TODO should we run "bashbrew-host-arch.sh" here instead?
ext='.exe'
fi
_download() {
# prefer wget, but "windows-2019" doesn't have it, so fall back to curl
local target="$1"; shift
local url="$1"; shift
if command -v wget > /dev/null; then
wget --timeout=5 -O "$target" "$url" --progress=dot:giga
else
curl -fL -o "$target" "$url"
fi
}
# https://github.com/docker-library/bashbrew/releases
[ -n "$BASHBREW_VERSION" ]
_download ".gha-bin/bashbrew$ext" "https://github.com/docker-library/bashbrew/releases/download/$BASHBREW_VERSION/bashbrew-$BASHBREW_ARCH$ext"
chmod +x ".gha-bin/bashbrew$ext"
".gha-bin/bashbrew$ext" --version
# https://doi-janky.infosiftr.net/job/wip/job/crane
_download ".gha-bin/crane$ext" "https://doi-janky.infosiftr.net/job/wip/job/crane/lastSuccessfulBuild/artifact/crane-$BASHBREW_ARCH$ext"
# TODO checksum verification ("checksums.txt")
chmod +x ".gha-bin/crane$ext"
".gha-bin/crane$ext" version
- name: JSON
id: json
run: |
json="$(
jq -L.scripts '
include "meta";
include "doi";
.[env.BUILD_ID]
| select(needs_build and .build.arch == env.BASHBREW_ARCH) # sanity check
| .commands = commands
| .shouldSign = build_should_sign
' builds.json
)"
[ -n "$json" ]
{
EOJSON="EOJSON-$RANDOM-$RANDOM-$RANDOM"
echo "json<<$EOJSON"
cat <<<"$json"
echo "$EOJSON"
} | tee -a "$GITHUB_ENV" "$GITHUB_OUTPUT" > /dev/null
mkdir build
- name: Check
run: |
img="$(jq <<<"$json" -r '.build.img')"
if crane digest "$img"; then
echo >&2 "error: '$img' already exists! cowardly refusing to overwrite it"
echo 'cancel=exists' >> "$GITHUB_OUTPUT"
else
echo 'cancel=' >> "$GITHUB_OUTPUT"
fi
id: check
- name: Cancel If Built
if: steps.check.outputs.cancel == 'exists'
uses: andymckay/cancel-action@435124153eb37d6a62a29d053a7e449652f89d51 # https://github.com/andymckay/cancel-action/commits/HEAD
# https://github.com/andymckay/cancel-action/issues/12
- name: Spin Wheels If Built (waiting for cancellation)
if: steps.check.outputs.cancel == 'exists'
run: |
while true; do
echo 'Waiting for build cancellation...'
sleep 30
done
exit 1
- name: Pull
run: |
cd build
shell="$(jq <<<"$json" -r '.commands.pull')"
eval "$shell"
- name: Build
run: |
cd build
shell="$(jq <<<"$json" -r '.commands.build')"
if grep <<<"$shell" -q ' buildx '; then
bk="$(../.doi/.bin/bashbrew-buildkit-env-setup.sh)"
bk="$(jq <<<"$bk" -r 'to_entries | map(.key + "=" + .value | @sh) | "export " + join(" ")')"
eval "$bk"
fi
eval "$shell"
# TODO signing prototype (see above where "shouldSign" is populated)
- name: Configure AWS (for signing)
if: fromJSON(steps.json.outputs.json).shouldSign
# https://github.com/aws-actions/configure-aws-credentials/releases
uses: aws-actions/configure-aws-credentials@010d0da01d0b5a38af31e9c3470dbfdabdecca3a # v4.0.1
with:
aws-region: ${{ github.ref_name == 'main' && secrets.AWS_KMS_PROD_REGION || secrets.AWS_KMS_STAGE_REGION }}
role-to-assume: ${{ github.ref_name == 'main' && secrets.AWS_KMS_PROD_ROLE_ARN || secrets.AWS_KMS_STAGE_ROLE_ARN }}
# TODO figure out if there's some way we could make our secrets ternaries here more DRY without major headaches 🙈
- name: Sign
if: fromJSON(steps.json.outputs.json).shouldSign
env:
AWS_KMS_REGION: ${{ github.ref_name == 'main' && secrets.AWS_KMS_PROD_REGION || secrets.AWS_KMS_STAGE_REGION }}
AWS_KMS_KEY_ARN: ${{ github.ref_name == 'main' && secrets.AWS_KMS_PROD_KEY_ARN || secrets.AWS_KMS_STAGE_KEY_ARN }}
run: |
cd build
args=(
--interactive
--rm
--read-only
--workdir /tmp # see "--tmpfs" below (TODO the signer currently uses PWD as TMPDIR -- something to fix in the future so we can drop this --workdir and only keep --tmpfs perhaps adding --env TMPDIR=/tmp if necessary)
)
if [ -t 0 ] && [ -t 1 ]; then
args+=( --tty )
fi
user="$(id -u)"
args+=( --tmpfs "/tmp:uid=$user" )
user+=":$(id -g)"
args+=( --user "$user" )
awsEnvs=( "${!AWS_@}" )
args+=( "${awsEnvs[@]/#/--env=}" )
# some very light assumption verification (see TODO in --mount below)
validate-oci-layout() {
local dir="$1"
jq -s '
if length != 1 then
error("unexpected 'oci-layout' document count: " + length)
else .[0] end
| if .imageLayoutVersion != "1.0.0" then
error("unsupported imageLayoutVersion: " + .imageLayoutVersion)
else . end
' "$dir/oci-layout" || return "$?"
jq -s '
if length != 1 then
error("unexpected 'index.json' document count: " + length)
else .[0] end
| if .schemaVersion != 2 then
error("unsupported schemaVersion: " + .schemaVersion)
else . end
| if .mediaType != "application/vnd.oci.image.index.v1+json" and .mediaType then # TODO drop the second half of this validation: https://github.com/moby/buildkit/issues/4595
error("unsupported index mediaType: " + .mediaType)
else . end
| if .manifests | length != 1 then
error("expected only one manifests entry, not " + (.manifests | length))
else . end
| .manifests[0] |= (
if .mediaType != "application/vnd.oci.image.index.v1+json" then
error("unsupported descriptor mediaType: " + .mediaType)
else . end
# TODO validate .digest somehow (`crane validate`?) - would also be good to validate all descriptors recursively
| if .size < 0 then
error("invalid descriptor size: " + .size)
else . end
)
' "$dir/index.json" || return "$?"
local manifest
manifest="$dir/blobs/$(jq -r '.manifests[0].digest | sub(":"; "/")' "$dir/index.json")" || return "$?"
jq -s '
if length != 1 then
error("unexpected image index document count: " + length)
else .[0] end
| if .schemaVersion != 2 then
error("unsupported schemaVersion: " + .schemaVersion)
else . end
| if .mediaType != "application/vnd.oci.image.index.v1+json" then
error("unsupported image index mediaType: " + .mediaType)
else . end
# TODO more validation?
' "$manifest" || return "$?"
}
validate-oci-layout temp
mkdir signed
args+=(
--mount "type=bind,src=$PWD/temp,dst=/doi-build/unsigned" # TODO this currently assumes normalized_builder == "buildkit" and !should_use_docker_buildx_driver -- we need to factor that in later (although this signs the attestations, not the image, so buildkit/buildx is the only builder whose output we *can* sign right now)
--mount "type=bind,src=$PWD/signed,dst=/doi-build/signed"
# https://explore.ggcr.dev/?repo=docker/image-signer-verifier
docker/image-signer-verifier:0.3.3@sha256:a5351e6495596429bacea85fbf8f41a77ce7237c26c74fd7c3b94c3e6d409c82
sign
--envelope-style oci-content-descriptor
--aws_region "$AWS_KMS_REGION"
--aws_arn "awskms:///$AWS_KMS_KEY_ARN"
--input oci:///doi-build/unsigned
--output oci:///doi-build/signed
)
docker run "${args[@]}"
validate-oci-layout signed
# TODO validate that "signed" still has all the original layer blobs from "temp" (ie, that the attestation manifest *just* has some new layers and everything else is unchanged)
rm -rf temp
mv signed temp
- name: Push
env:
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }}
run: |
export DOCKER_CONFIG="$PWD/.docker"
mkdir "$DOCKER_CONFIG"
trap 'find "$DOCKER_CONFIG" -type f -exec shred -fuvz "{}" + || :; rm -rf "$DOCKER_CONFIG"' EXIT
docker login --username "$DOCKER_HUB_USERNAME" --password-stdin <<<"$DOCKER_HUB_PASSWORD"
unset DOCKER_HUB_USERNAME DOCKER_HUB_PASSWORD
cd build
shell="$(jq <<<"$json" -r '.commands.push')"
eval "$shell"