-
Notifications
You must be signed in to change notification settings - Fork 6
297 lines (269 loc) Β· 12.3 KB
/
Copy pathbuild-test-push.yml
File metadata and controls
297 lines (269 loc) Β· 12.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
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
# Required repository secrets:
# DOCKERHUB_USERNAME - Docker Hub account username, used for `docker login` and pushes.
# DOCKERHUB_TOKEN - Docker Hub access token (NOT an account password; a 2FA-protected
# password cannot be used) with "Read, Write, Delete" scope. Pushing
# images only needs Read & Write, but peter-evans/dockerhub-description
# requires the Delete scope as well -- a Read & Write token returns
# 403 Forbidden when updating the description. Create one at
# https://hub.docker.com/settings/security.
name: build-test-push
on:
push:
branches:
- master
pull_request:
concurrency:
# Cancel superseded runs on a branch/PR when a new commit is pushed, but let master
# runs finish so an in-flight publish isn't interrupted (half-built manifest list or
# an unpublished directory).
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
env:
REPO_NAME: emmercm/libtorrent
jobs:
lint:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
directory:
- "0.16"
- "1.0"
- "1.1"
- "1.2-py2"
- "1.2-py3"
- "2.0"
- "2.1"
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Run hadolint
# Pinned to a full version: hadolint-action does not publish a moving major tag
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
with:
dockerfile: ${{ matrix.directory }}/Dockerfile
# Decide which directories to build: those whose files changed, plus any whose
# image tag is not yet published (e.g. a version bump, which changes this file
# rather than the directory). Unchanged, already-published directories are skipped.
setup:
runs-on: ubuntu-latest
outputs:
# build job's matrix: cross product of selected directories and platforms
build_matrix: ${{ steps.select.outputs.build_matrix }}
# merge job's matrix: selected directories only (one manifest list per directory)
merge_matrix: ${{ steps.select.outputs.merge_matrix }}
# whether any directory was selected to build at all
has_builds: ${{ steps.select.outputs.has_builds }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Detect changed directories
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4
id: filter
with:
filters: |
"0.16": "0.16/**"
"1.0": "1.0/**"
"1.1": "1.1/**"
"1.2-py2": "1.2-py2/**"
"1.2-py3": "1.2-py3/**"
"2.0": "2.0/**"
"2.1": "2.1/**"
- name: Select directories to build
id: select
env:
CHANGED: ${{ steps.filter.outputs.changes }}
# Buildable directories with their libtorrent version and extra tags.
# The implicit ${VERSION} and ${VERSION}-alpine tags are always added too.
# https://github.com/arvidn/libtorrent/releases
CANDIDATES: |
[
{"directory": "0.16", "version": "0.16.17", "tags": "0.16 0.16-alpine 0 0-alpine"},
{"directory": "1.0", "version": "1.0.11", "tags": "1.0 1.0-alpine"},
{"directory": "1.1", "version": "1.1.14", "tags": "1.1 1.1-alpine"},
{"directory": "1.2-py2", "version": "1.2.11", "tags": ""},
{"directory": "1.2-py3", "version": "1.2.20", "tags": "1.2 1.2-alpine 1 1-alpine"},
{"directory": "2.0", "version": "2.0.13", "tags": "2.0 2.0-alpine"},
{"directory": "2.1", "version": "2.1.0", "tags": "2.1 2.1-alpine 2 2-alpine latest"}
]
run: |
SELECTED='[]'
while read -r ENTRY; do
DIRECTORY=$(jq -r '.directory' <<< "${ENTRY}")
VERSION=$(jq -r '.version' <<< "${ENTRY}")
if jq -e --arg d "${DIRECTORY}" 'index($d) != null' <<< "${CHANGED}" > /dev/null; then
echo "${DIRECTORY}: files changed, building"
elif ! curl -fsS "https://hub.docker.com/v2/repositories/${REPO_NAME}/tags/${VERSION}" &> /dev/null; then
echo "${DIRECTORY}: ${REPO_NAME}:${VERSION} not yet published, building"
else
echo "${DIRECTORY}: unchanged and already published, skipping"
continue
fi
SELECTED=$(jq -c ". + [${ENTRY}]" <<< "${SELECTED}")
done < <(jq -c '.[]' <<< "${CANDIDATES}")
# Each platform builds on the closest runner architecture to minimize QEMU
# emulation: amd64 natively on amd64, and the arm variants on ARM hosts
# (arm64v8 natively; armv7/armv6 via aarch32 emulation).
PLATFORMS='[
{"name": "linux/amd64", "slug": "amd64", "runner": "ubuntu-latest"},
{"name": "linux/arm64/v8", "slug": "arm64v8", "runner": "ubuntu-24.04-arm"},
{"name": "linux/arm/v7", "slug": "armv7", "runner": "ubuntu-24.04-arm"},
{"name": "linux/arm/v6", "slug": "armv6", "runner": "ubuntu-24.04-arm"}
]'
echo "Selected directories: ${SELECTED}"
echo "merge_matrix={\"include\":${SELECTED}}" >> "${GITHUB_OUTPUT}"
echo "build_matrix={\"entry\":${SELECTED},\"platform\":$(jq -c . <<< "${PLATFORMS}")}" >> "${GITHUB_OUTPUT}"
if [[ "${SELECTED}" == "[]" ]]; then
echo "has_builds=false" >> "${GITHUB_OUTPUT}"
else
echo "has_builds=true" >> "${GITHUB_OUTPUT}"
fi
# One job per (directory, platform). Each builds and tests a single-platform image,
# then (on master) pushes it to the registry by digest only -- no tags. The tags are
# applied later by the merge job, so nothing usable is published until everything passes.
build:
needs:
- lint
- setup
if: needs.setup.outputs.has_builds == 'true'
runs-on: ${{ matrix.platform.runner }}
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup.outputs.build_matrix) }}
env:
DIRECTORY: ${{ matrix.entry.directory }}
VERSION: ${{ matrix.entry.version }}
PLATFORM: ${{ matrix.platform.name }}
# Cache scope keeps each directory+platform's layers separate
CACHE_SCOPE: ${{ matrix.entry.directory }}-${{ matrix.platform.slug }}
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
# Needed to run the armv7/armv6 (aarch32) images on the arm64 host during testing
- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
- name: Build image for testing
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
with:
context: ${{ env.DIRECTORY }}
platforms: ${{ env.PLATFORM }}
build-args: |
VERSION=${{ env.VERSION }}
tags: ${{ env.REPO_NAME }}:${{ env.VERSION }}
load: true
cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
cache-to: type=gha,mode=max,scope=${{ env.CACHE_SCOPE }}
- name: Test image
run: |
# container-structure-test runs on the host, so fetch the runner's architecture
case "${RUNNER_ARCH}" in
X64) CST_ARCH=amd64 ;;
ARM64) CST_ARCH=arm64 ;;
*) echo "Unsupported runner architecture: ${RUNNER_ARCH}" >&2; exit 1 ;;
esac
curl -LO "https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-${CST_ARCH}"
chmod +x "container-structure-test-linux-${CST_ARCH}"
sudo mv "container-structure-test-linux-${CST_ARCH}" /usr/local/bin/container-structure-test
container-structure-test test --config "${DIRECTORY}/container-structure-test.yml" --image "${REPO_NAME}:${VERSION}"
- name: Log in to Docker Hub
if: github.ref == 'refs/heads/master'
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Push image by digest
id: push
if: github.ref == 'refs/heads/master'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
with:
context: ${{ env.DIRECTORY }}
platforms: ${{ env.PLATFORM }}
build-args: |
VERSION=${{ env.VERSION }}
outputs: type=image,name=${{ env.REPO_NAME }},push-by-digest=true,name-canonical=true,push=true
cache-from: type=gha,scope=${{ env.CACHE_SCOPE }}
- name: Export digest
if: github.ref == 'refs/heads/master'
run: |
mkdir -p digests
echo "${{ steps.push.outputs.digest }}" > "digests/${{ matrix.platform.slug }}"
- name: Upload digest
if: github.ref == 'refs/heads/master'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: digests-${{ matrix.entry.directory }}-${{ matrix.platform.slug }}
path: digests/*
if-no-files-found: error
# One job per directory: combine its per-platform digests into a single multi-arch
# manifest list and apply all of the directory's tags. Runs only after every build
# job succeeded, so a failure anywhere prevents any tags from being published.
merge:
needs:
- setup
- build
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup.outputs.merge_matrix) }}
env:
VERSION: ${{ matrix.version }}
TAGS: ${{ matrix.tags }}
steps:
- name: Download digests
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
pattern: digests-${{ matrix.directory }}-*
path: digests
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
- name: Log in to Docker Hub
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Create and push manifest list
run: |
TAG_ARGS=()
for TAG in "${VERSION}" "${VERSION}-alpine" ${TAGS}; do
TAG_ARGS+=(--tag "${REPO_NAME}:${TAG}")
done
SOURCES=()
for FILE in digests/*; do
SOURCES+=("${REPO_NAME}@$(cat "${FILE}")")
done
docker buildx imagetools create "${TAG_ARGS[@]}" "${SOURCES[@]}"
- name: Inspect manifest list
run: docker buildx imagetools inspect "${REPO_NAME}:${VERSION}"
# Independent of the image build: the Docker Hub description tracks README.md, so
# it is updated on every master push even when no directory needed rebuilding.
update-description:
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Read short description from README
id: readme
run: echo "short=$(grep '^\w' README.md | head -1)" >> "${GITHUB_OUTPUT}"
- name: Update Docker Hub description
# Can't use 2FA: https://github.com/docker/roadmap/issues/115
uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa # v5
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
repository: ${{ env.REPO_NAME }}
short-description: ${{ steps.readme.outputs.short }}
# !!! This check should be required by GitHub !!!
build-status-check:
if: always()
needs:
- lint
- setup
- build
runs-on: ubuntu-latest
steps:
- name: All checks passed
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
with:
allowed-skips: build
jobs: ${{ toJSON(needs) }}