Skip to content

Commit fb7e1ef

Browse files
hoverkraft-bot[bot]neilime
authored andcommitted
feat(docker-build-images): optimize caching and signing
Signed-off-by: Emilien Escalle <emilien.escalle@escemi.com>
1 parent dc183c5 commit fb7e1ef

8 files changed

Lines changed: 183 additions & 107 deletions

File tree

.github/workflows/__test-workflow-docker-build-images.yml

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ permissions:
1212
pull-requests: read
1313
id-token: write
1414

15+
env:
16+
SECRET_ENV_TEST: "test-secret-env"
17+
SECRET_ENV_ANOTHER_TEST: "another-test-secret-env"
18+
1519
# jscpd:ignore-start
1620
jobs:
1721
arrange:
@@ -105,6 +109,11 @@ jobs:
105109
)
106110
);
107111
112+
assert.equal(applicationMultiArchImage.platforms.length, 3);
113+
assert(applicationMultiArchImage.platforms.includes("linux/amd64"));
114+
assert(applicationMultiArchImage.platforms.includes("linux/arm64"));
115+
assert(applicationMultiArchImage.platforms.includes("linux/arm/v7"));
116+
108117
const applicationMonoArchImage = builtImages["test-mono-arch"];
109118
110119
assert.equal(applicationMonoArchImage.name, "test-mono-arch");
@@ -121,6 +130,8 @@ jobs:
121130
applicationMonoArchImage.images[0],
122131
`ghcr.io/hoverkraft-tech/ci-github-container/test-mono-arch:0.1.0@${applicationMonoArchImage.digest}`
123132
);
133+
assert.equal(applicationMonoArchImage.platforms.length, 1);
134+
assert(applicationMonoArchImage.platforms.includes("linux/amd64"));
124135
125136
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
126137
with:
@@ -278,8 +289,8 @@ jobs:
278289
secrets:
279290
oci-registry-password: ${{ secrets.GITHUB_TOKEN }}
280291
build-secrets: |
281-
SECRET_REPOSITORY_OWNER=${{ github.repository_owner }}
282-
SECRET_REPOSITORY=${{ github.repository }}
292+
SECRET_TEST=test-secret
293+
SECRET_ANOTHER_TEST=another-test-secret
283294
build-secret-github-app-key: ${{ secrets.CI_BOT_APP_PRIVATE_KEY }}
284295
with:
285296
cache-type: registry
@@ -293,12 +304,12 @@ jobs:
293304
"platforms": ["linux/amd64","linux/arm64"],
294305
"build-args": {
295306
"BUILD_RUN_ID": "${{ github.run_id }}",
296-
"BUILD_REPOSITORY_OWNER": "${{ github.repository_owner }}",
297-
"BUILD_REPOSITORY": "${{ github.repository }}"
307+
"BUILD_ARG_TEST": "test-arg",
308+
"BUILD_ARG_ANOTHER_TEST": "another-test-arg"
298309
},
299310
"secret-envs": {
300-
"SECRET_ENV_REPOSITORY_OWNER": "GITHUB_REPOSITORY_OWNER",
301-
"SECRET_ENV_REPOSITORY": "GITHUB_REPOSITORY"
311+
"SECRET_ENV_TEST": "SECRET_ENV_TEST",
312+
"SECRET_ENV_ANOTHER_TEST": "SECRET_ENV_ANOTHER_TEST"
302313
}
303314
}
304315
]

.github/workflows/docker-build-images.yml

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,6 @@ name: Docker build images
66

77
on: # yamllint disable-line rule:truthy
88
workflow_call:
9-
outputs:
10-
built-images:
11-
description: |
12-
Built images data.
13-
Example:
14-
```json
15-
{
16-
"application": {
17-
"name": "application",
18-
"registry": "ghcr.io",
19-
"repository": "my-org/my-repo/application",
20-
"tags": ["pr-63-5222075","pr-63"],
21-
"images": [
22-
"ghcr.io/my-org/my-repo/application:pr-63-5222075@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
23-
"ghcr.io/my-org/my-repo/application:pr-63@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d"
24-
],
25-
"digest": "sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
26-
"annotations": {
27-
"org.opencontainers.image.created": "2021-09-30T14:00:00Z",
28-
"org.opencontainers.image.description": "Application image"
29-
}
30-
}
31-
}
32-
```
33-
value: ${{ jobs.publish-manifests.outputs.built-images }}
349
inputs:
3510
runs-on:
3611
description: |
@@ -141,6 +116,32 @@ on: # yamllint disable-line rule:truthy
141116
GitHub App private key to generate GitHub token to be passed as build secret env.
142117
See https://github.com/actions/create-github-app-token.
143118
required: false
119+
outputs:
120+
built-images:
121+
description: |
122+
Built images data.
123+
Example:
124+
```json
125+
{
126+
"application": {
127+
"name": "application",
128+
"registry": "ghcr.io",
129+
"repository": "my-org/my-repo/application",
130+
"tags": ["pr-63-5222075","pr-63"],
131+
"images": [
132+
"ghcr.io/my-org/my-repo/application:pr-63-5222075@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
133+
"ghcr.io/my-org/my-repo/application:pr-63@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d"
134+
],
135+
"digest": "sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
136+
"annotations": {
137+
"org.opencontainers.image.created": "2021-09-30T14:00:00Z",
138+
"org.opencontainers.image.description": "Application image"
139+
},
140+
"platforms": ["linux/amd64", "linux/arm64"]
141+
}
142+
}
143+
```
144+
value: ${{ jobs.publish-manifests.outputs.built-images }}
144145

145146
permissions:
146147
contents: read
@@ -474,15 +475,17 @@ jobs:
474475
// Group by image name
475476
const images = {};
476477
builtImages.forEach(builtImage => {
477-
const { name, image, ...rest } = builtImage;
478+
const { name, image, platform, ...rest } = builtImage;
478479
if (!images[name]) {
479480
images[name] = {
480481
name,
481482
images: [image],
483+
platforms: [platform],
482484
...rest,
483485
};
484486
} else {
485487
images[name].images = [...new Set([...images[name].images, image])];
488+
images[name].platforms = [...new Set([...images[name].platforms, platform])];
486489
}
487490
});
488491

actions/docker/build-image/action.yml

Lines changed: 100 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,30 +10,6 @@ branding:
1010
icon: package
1111
color: blue
1212

13-
outputs:
14-
built-image:
15-
description: |
16-
Built image data.
17-
Example:
18-
```json
19-
{
20-
"name": "application",
21-
"registry": "ghcr.io",
22-
"repository": "my-org/my-repo/application",
23-
"digest": "sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
24-
"image": "ghcr.io/my-org/my-repo/application@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
25-
"tags": [
26-
"pr-63-5222075",
27-
"pr-63"
28-
],
29-
"annotations": {
30-
"org.opencontainers.image.created": "2021-09-30T14:00:00Z",
31-
"org.opencontainers.image.description": "Application image"
32-
}
33-
}
34-
```
35-
value: ${{ steps.get-built-image.outputs.built-image }}
36-
3713
inputs:
3814
oci-registry:
3915
description: "OCI registry where to pull and push images"
@@ -70,7 +46,7 @@ inputs:
7046
required: false
7147
platform:
7248
description: |
73-
Platform to build for.
49+
Platform to build for. Example: `linux/amd64`.
7450
See https://github.com/docker/build-push-action#inputs.
7551
required: true
7652
context:
@@ -112,6 +88,31 @@ inputs:
11288
default: "gha"
11389
required: false
11490

91+
outputs:
92+
built-image:
93+
description: |
94+
Built image data.
95+
Example:
96+
```json
97+
{
98+
"name": "application",
99+
"registry": "ghcr.io",
100+
"repository": "my-org/my-repo/application",
101+
"digest": "sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
102+
"image": "ghcr.io/my-org/my-repo/application@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
103+
"tags": [
104+
"pr-63-5222075",
105+
"pr-63"
106+
],
107+
"annotations": {
108+
"org.opencontainers.image.created": "2021-09-30T14:00:00Z",
109+
"org.opencontainers.image.description": "Application image"
110+
},
111+
"platform": "linux/amd64"
112+
}
113+
```
114+
value: ${{ steps.get-built-image.outputs.built-image }}
115+
115116
runs:
116117
using: "composite"
117118
steps:
@@ -142,6 +143,14 @@ runs:
142143
- id: get-docker-config
143144
shell: bash
144145
run: |
146+
DOCKERFILE_PATH="${{ github.workspace }}/${{ inputs.context }}/${{ inputs.dockerfile }}"
147+
if [ ! -f "$DOCKERFILE_PATH" ]; then
148+
echo "::error::Dockerfile not found at path: $DOCKERFILE_PATH"
149+
exit 1
150+
fi
151+
DOCKERFILE_PATH=$(realpath "$DOCKERFILE_PATH")
152+
echo "dockerfile-path=$DOCKERFILE_PATH" >> "$GITHUB_OUTPUT"
153+
145154
TAG_SUFFIX="-${{ steps.slugify-platform.outputs.result }}"
146155
147156
# Add tag suffix flavor
@@ -201,7 +210,24 @@ runs:
201210
fi
202211
fi
203212
204-
- id: cache
213+
- if: steps.get-docker-config.outputs.docker-exists != 'true'
214+
uses: docker/setup-docker-action@b60f85385d03ac8acfca6d9996982511d8620a19 # v4.3.0
215+
216+
- if: steps.get-docker-config.outputs.platform-exists != 'true'
217+
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
218+
with:
219+
platforms: ${{ inputs.platform }}
220+
221+
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
222+
with:
223+
# FIXME: upgrade version when available (https://github.com/docker/buildx/releases)
224+
version: v0.29.1
225+
# FIXME: upgrade version when available (https://hub.docker.com/r/moby/buildkit)
226+
driver-opts: |
227+
image=moby/buildkit:v0.25.1
228+
229+
# Caching setup
230+
- id: cache-arguments
205231
uses: int128/docker-build-cache-config-action@b3744816a5ba1ad9885b634d530b35ca90ece622 # v1.39.0
206232
with:
207233
image: ${{ steps.get-docker-config.outputs.cache-image }}
@@ -216,8 +242,8 @@ runs:
216242
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
217243
with:
218244
script: |
219-
const cacheFrom = `${{ steps.cache.outputs.cache-from }}`;
220-
const cacheTo = `${{ steps.cache.outputs.cache-to }}`;
245+
const cacheFrom = `${{ steps.cache-arguments.outputs.cache-from }}`;
246+
const cacheTo = `${{ steps.cache-arguments.outputs.cache-to }}`;
221247
222248
core.info(`Original cache-from: ${cacheFrom}`);
223249
core.info(`Original cache-to: ${cacheTo}`);
@@ -234,21 +260,19 @@ runs:
234260
core.setOutput('cache-from', transformedCacheFrom);
235261
core.setOutput('cache-to', transformedCacheTo);
236262
237-
- if: steps.get-docker-config.outputs.docker-exists != 'true'
238-
uses: docker/setup-docker-action@b60f85385d03ac8acfca6d9996982511d8620a19 # v4.3.0
239-
240-
- if: steps.get-docker-config.outputs.platform-exists != 'true'
241-
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
263+
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
264+
id: cache
242265
with:
243-
platforms: ${{ inputs.platform }}
266+
path: cache-mount
267+
key: cache-mount-${{ hashFiles(steps.get-docker-config.outputs.dockerfile-path) }}
244268

245-
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
269+
- name: Restore Docker cache mounts
270+
uses: reproducible-containers/buildkit-cache-dance@5b81f4d29dc8397a7d341dba3aeecc7ec54d6361 # v3.3.0
246271
with:
247-
# FIXME: upgrade version when available (https://github.com/docker/buildx/releases)
248-
version: v0.27.0
249-
# FIXME: upgrade version when available (https://hub.docker.com/r/moby/buildkit)
250-
driver-opts: |
251-
image=moby/buildkit:v0.23.2
272+
builder: ${{ steps.setup-buildx.outputs.name }}
273+
cache-dir: cache-mount
274+
dockerfile: ${{ steps.get-docker-config.outputs.dockerfile-path }}
275+
skip-extraction: ${{ steps.cache.outputs.cache-hit }}
252276

253277
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
254278
with:
@@ -262,16 +286,16 @@ runs:
262286
context: ${{ inputs.context }}
263287
build-args: ${{ inputs.build-args }}
264288
target: ${{ inputs.target }}
265-
file: ${{ github.workspace }}/${{ inputs.context }}/${{ inputs.dockerfile }}
289+
file: ${{ steps.get-docker-config.outputs.dockerfile-path }}
266290
secrets: ${{ inputs.secrets }}
267291
secret-envs: ${{ inputs.secret-envs }}
268292
platforms: ${{ inputs.platform }}
269293
# FIXME: Remove 'inputs.cache-type == 'gha' && steps.transform-cache-gha.outputs.cache-from ||'
270294
# when https://github.com/int128/docker-build-cache-config-action/pull/1213 is merged
271-
cache-from: ${{ inputs.cache-type == 'gha' && steps.transform-cache-gha.outputs.cache-from || steps.cache.outputs.cache-from }}
295+
cache-from: ${{ inputs.cache-type == 'gha' && steps.transform-cache-gha.outputs.cache-from || steps.cache-arguments.outputs.cache-from }}
272296
# FIXME: Remove 'inputs.cache-type == 'gha' && steps.transform-cache-gha.outputs.cache-to ||'
273297
# when https://github.com/int128/docker-build-cache-config-action/pull/1213 is merged
274-
cache-to: ${{ inputs.cache-type == 'gha' && steps.transform-cache-gha.outputs.cache-to || steps.cache.outputs.cache-to }}
298+
cache-to: ${{ inputs.cache-type == 'gha' && steps.transform-cache-gha.outputs.cache-to || steps.cache-arguments.outputs.cache-to }}
275299
outputs: "type=image,push=true,push-by-digest=true,name-canonical=true,name=${{ steps.metadata.outputs.image }}"
276300
labels: ${{ steps.metadata.outputs.labels }}
277301
annotations: ${{ steps.metadata.outputs.annotations }}
@@ -286,10 +310,6 @@ runs:
286310
return;
287311
}
288312
289-
if (builtMetadata["containerimage.digest"] === undefined) {
290-
return core.setFailed('Given "metadata"."containerimage.digest" output is undefined.');
291-
}
292-
293313
const name = `${{ inputs.image }}`;
294314
const image = `${{ steps.metadata.outputs.image }}`;
295315
const registryMatch = image.match(/^([^\/]+)\/.*/);
@@ -302,6 +322,37 @@ runs:
302322
.map(tag => tag.replace(/[^\/]+\/[^:]+:(.+)/,'$1').trim())
303323
.filter(tag => tag !== "");
304324
325+
let platform;
326+
327+
const buildxProvenance = builtMetadata?.["buildx.build.provenance"];
328+
if (buildxProvenance !== undefined) {
329+
platform = buildxProvenance.invocation?.environment?.platform;
330+
if (platform === undefined) {
331+
return core.setFailed('Given "metadata"."buildx.build.provenance"."invocation"."environment"."platform" output is undefined.');
332+
}
333+
if (typeof platform !== "string") {
334+
return core.setFailed('Given "metadata"."buildx.build.provenance"."invocation"."environment"."platform" is not a string.');
335+
}
336+
platform = platform.trim();
337+
if (platform === "") {
338+
return core.setFailed('Given "metadata"."buildx.build.provenance"."invocation"."environment"."platform" is empty.');
339+
}
340+
} else {
341+
const descriptor = builtMetadata?.["containerimage.descriptor"];
342+
if (descriptor?.["platform"] === undefined) {
343+
return core.setFailed('Given "metadata"."containerimage.descriptor"."platform" output is undefined.');
344+
}
345+
const platformData = descriptor["platform"];
346+
if (typeof platformData !== 'object' || platformData.os === undefined || platformData.architecture === undefined) {
347+
return core.setFailed('Given "metadata"."containerimage.descriptor"."platform" does not contain required "os" and "architecture" fields.');
348+
}
349+
platform = `${platformData.os}/${platformData.architecture}${platformData.variant ? `/${platformData.variant}` : ''}`;
350+
}
351+
352+
if (builtMetadata?.["containerimage.digest"] === undefined) {
353+
return core.setFailed('Given "metadata"."containerimage.digest" output is undefined.');
354+
}
355+
305356
const digests = builtMetadata["containerimage.digest"]
306357
.split(",")
307358
.map(digest => {
@@ -344,7 +395,8 @@ runs:
344395
registry,
345396
repository,
346397
image: imageWithDigest,
347-
digest
398+
digest,
399+
platform
348400
};
349401
350402
core.setOutput("built-image", JSON.stringify(builtImage));

0 commit comments

Comments
 (0)