Skip to content

Commit 9a6f9d6

Browse files
committed
feat(docker)!: add digest to images uri
Signed-off-by: Emilien Escalle <emilien.escalle@escemi.com>
1 parent d78340e commit 9a6f9d6

File tree

9 files changed

+234
-190
lines changed

9 files changed

+234
-190
lines changed

.github/workflows/__shared-ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
linter:
1919
uses: hoverkraft-tech/ci-github-common/.github/workflows/linter.yml@6857ef6d10f704e0998aa4955282f27d1b9be778 # 0.23.1
2020
with:
21-
# FIXME: Remove this on next super-linter release
21+
# FIXME: Remove useless linters on next super-linter release
2222
linter-env: |
2323
VALIDATE_JAVASCRIPT_PRETTIER=false
2424
VALIDATE_KUBERNETES_KUBECONFORM=false

.github/workflows/__test-action-docker-build-image.yml

Lines changed: 26 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,12 @@ jobs:
6565
"hoverkraft-tech/ci-github-container/application-test",
6666
`"repository" output is not valid`
6767
);
68-
assert.equal(
69-
builtImage.digests.length,
70-
1,
71-
`"digests" output is not valid`
72-
);
7368
assert.match(
74-
builtImage.digests[0],
75-
/^ghcr\.io\/hoverkraft-tech\/ci-github-container\/application-test@sha256:[a-f0-9]{64}$/,
76-
`"digests" output is not valid`
69+
builtImage.digest,
70+
/^sha256:[a-f0-9]{64}$/,
71+
`"digest" output is not valid`
7772
);
73+
assert.equal(builtImage.image, `ghcr.io/hoverkraft-tech/ci-github-container/application-test@${builtImage.digest}`, `"image" output is not valid`);
7874
7975
// Annotations
8076
assert.match(
@@ -123,47 +119,18 @@ jobs:
123119
assert.equal(builtImage.tags[0], prShaTag, `"tags" output is not valid`);
124120
assert.equal(builtImage.tags[1], prTag, `"tags" output is not valid`);
125121
126-
assert.equal(
127-
builtImage.images.length,
128-
2,
129-
`"images" output is not valid`
130-
);
131-
assert.equal(
132-
builtImage.images[0],
133-
`ghcr.io/hoverkraft-tech/ci-github-container/application-test:${prShaTag}`,
134-
`"images" output is not valid`
135-
);
136-
assert.equal(
137-
builtImage.images[1],
138-
`ghcr.io/hoverkraft-tech/ci-github-container/application-test:${prTag}`,
139-
`"images" output is not valid`
140-
);
141122
assert.equal(
142123
builtImage.annotations["org.opencontainers.image.version"],
143124
prTag,
144125
`"annotations.org.opencontainers.image.version" output is not valid`
145126
);
146-
147127
} else {
148128
const refTag = `${{ github.ref_name }}`;
149129
150130
assert.equal(builtImage.tags.length, 2, `"tags" output is not valid`);
151131
assert.equal(builtImage.tags[0], refTag, `"tags" output is not valid`);
152132
assert.equal(builtImage.tags[1], "latest", `"tags" output is not valid`);
153133
154-
assert.equal(builtImage.images.length, 2, `"images" output is not valid`);
155-
assert.equal(
156-
builtImage.images[0],
157-
`ghcr.io/hoverkraft-tech/ci-github-container/application-test:${refTag}`,
158-
`"images" output is not valid`
159-
);
160-
161-
assert.equal(
162-
builtImage.images[1],
163-
`ghcr.io/hoverkraft-tech/ci-github-container/application-test:latest`,
164-
`"images" output is not valid`
165-
);
166-
167134
assert.equal(
168135
builtImage.annotations["org.opencontainers.image.version"],
169136
refTag,
@@ -177,20 +144,18 @@ jobs:
177144
username: ${{ github.repository_owner }}
178145
password: ${{ github.token }}
179146

180-
- name: Assert - Check docker digests
147+
- name: Assert - Check docker image
181148
run: |
182-
DIGESTS=$(echo '${{ steps.build-image.outputs.built-image }}' | jq -r '.digests[]')
183-
for DIGEST in $DIGESTS; do
184-
if ! docker pull "$DIGEST"; then
185-
echo "Failed to pull $DIGEST"
186-
exit 1
187-
fi
188-
189-
if ! docker manifest inspect "$DIGEST"; then
190-
echo "Failed to inspect $DIGEST"
191-
exit 1
192-
fi
193-
done
149+
IMAGE=$(echo '${{ steps.build-image.outputs.built-image }}' | jq -r '.image')
150+
if ! docker pull "$IMAGE"; then
151+
echo "Failed to pull $IMAGE"
152+
exit 1
153+
fi
154+
155+
if ! docker manifest inspect "$IMAGE"; then
156+
echo "Failed to inspect $IMAGE"
157+
exit 1
158+
fi
194159
195160
tests-with-given-tag:
196161
name: Test for "docker/build-image" action with given tag
@@ -249,19 +214,17 @@ jobs:
249214
username: ${{ github.repository_owner }}
250215
password: ${{ github.token }}
251216

252-
- name: Assert - Check docker digests
217+
- name: Assert - Check docker image
253218
run: |
254-
DIGESTS=$(echo '${{ steps.build-image.outputs.built-image }}' | jq -r '.digests[]')
255-
for DIGEST in $DIGESTS; do
256-
if ! docker pull "$DIGEST"; then
257-
echo "Failed to pull $DIGEST"
258-
exit 1
259-
fi
260-
261-
if ! docker manifest inspect "$DIGEST"; then
262-
echo "Failed to inspect $DIGEST"
263-
exit 1
264-
fi
265-
done
219+
IMAGE=$(echo '${{ steps.build-image.outputs.built-image }}' | jq -r '.image')
220+
if ! docker pull "$IMAGE"; then
221+
echo "Failed to pull $IMAGE"
222+
exit 1
223+
fi
224+
225+
if ! docker manifest inspect "$IMAGE"; then
226+
echo "Failed to inspect $IMAGE"
227+
exit 1
228+
fi
266229
267230
# jscpd:ignore-end

.github/workflows/__test-action-docker-prune-pull-requests-image-tags.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ jobs:
155155
156156
echo "$MANIFEST"
157157
158-
# Ensure all manifest digests didn't get deleted
158+
# Ensure all manifests digest didn't get deleted
159159
for DIGEST in $(echo "$MANIFEST" | jq -r '.manifests[].digest'); do
160160
IMAGE_MANIFEST="ghcr.io/hoverkraft-tech/ci-github-container/${{ env.IMAGE }}@$DIGEST"
161161
docker pull "$IMAGE_MANIFEST"

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

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -90,42 +90,36 @@ jobs:
9090
}
9191
9292
const applicationMultiArchImage = builtImages["test-multi-arch"];
93+
9394
assert.equal(applicationMultiArchImage.name, "test-multi-arch");
9495
assert.equal(applicationMultiArchImage.registry, "ghcr.io");
9596
assert.equal(applicationMultiArchImage.repository,"hoverkraft-tech/ci-github-container/test-multi-arch");
97+
assert.match(applicationMultiArchImage.digest, /^sha256:[0-9a-f]{64}$/);
98+
9699
assert(applicationMultiArchImage.tags.length);
97100
assert(applicationMultiArchImage.images.length);
98101
applicationMultiArchImage.images.forEach(
99102
image => assert.match(
100103
image,
101-
/^ghcr\.io\/hoverkraft-tech\/ci-github-container\/test-multi-arch:[\.a-z0-9-]+$/
104+
/^ghcr\.io\/hoverkraft-tech\/ci-github-container\/test-multi-arch:[\.a-z0-9-]+@sha256:[0-9a-f]{64}$/
102105
)
103106
);
104107
105108
const applicationMonoArchImage = builtImages["test-mono-arch"];
106-
assert.equal(
107-
applicationMonoArchImage.name,
108-
"test-mono-arch"
109-
);
110-
assert.equal(
111-
applicationMonoArchImage.registry,
112-
"ghcr.io"
113-
);
109+
110+
assert.equal(applicationMonoArchImage.name, "test-mono-arch");
111+
assert.equal(applicationMonoArchImage.registry, "ghcr.io");
114112
assert.equal(
115113
applicationMonoArchImage.repository,
116114
"hoverkraft-tech/ci-github-container/test-mono-arch"
117115
);
118-
assert.equal(
119-
applicationMonoArchImage.tags.length,
120-
1
121-
);
122-
assert.equal(
123-
applicationMonoArchImage.images.length,
124-
1
125-
);
116+
assert.match(applicationMonoArchImage.digest, /^sha256:[0-9a-f]{64}$/);
117+
118+
assert.equal(applicationMonoArchImage.tags.length, 1);
119+
assert.equal(applicationMonoArchImage.images.length, 1);
126120
assert.equal(
127121
applicationMonoArchImage.images[0],
128-
"ghcr.io/hoverkraft-tech/ci-github-container/test-mono-arch:0.1.0"
122+
`ghcr.io/hoverkraft-tech/ci-github-container/test-mono-arch:0.1.0@${applicationMonoArchImage.digest}`
129123
);
130124
131125
- uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
@@ -367,8 +361,12 @@ jobs:
367361
expectedTag = `${{ github.ref_name }}`;
368362
}
369363
364+
const digest = `${{ fromJson(needs.act-build-args-secrets-and-registry-caching.outputs.built-images).test-build-args-secrets.digest }}`;
365+
assert(digest.length, `"built-images" output does not contain digest for "test-build-args-secrets" image`);
366+
assert.match(digest, /^sha256:[0-9a-f]{64}$/, `"built-images" output does not contain valid digest for "test-build-args-secrets" image`);
367+
370368
const expectedImage = `ghcr.io/hoverkraft-tech/ci-github-container/test-build-args-secrets`;
371-
const expectedImageTag = `${expectedImage}:${expectedTag}`;
369+
const expectedImageTag = `${expectedImage}:${expectedTag}@${digest}`;
372370
373371
const image = `${{ fromJson(needs.act-build-args-secrets-and-registry-caching.outputs.built-images).test-build-args-secrets.images[0] }}`;
374372

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -187,15 +187,15 @@ If a platform entry omits the <code>runs-on</code> field, the following default
187187

188188
### Built image data
189189

190-
| **Parameter** | **Description** | **Example** |
191-
| ---------------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
192-
| **<code>name</code>** | Image name | `application` |
193-
| **<code>registry</code>** | Registry where the image is stored | `ghcr.io` |
194-
| **<code>repository</code>** | Repository name | `my-org/my-repo/application` |
195-
| **<code>tags</code>** | List of tags | `["pr-63-5222075","pr-63"]` |
196-
| **<code>images</code>** | List of images | `["ghcr.io/my-org/my-repo/application:pr-63-5222075","ghcr.io/my-org/my-repo/application:pr-63"]` |
197-
| **<code>digests</code>** | List of digests | `["ghcr.io/my-org/my-repo/application@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d"]` |
198-
| **<code>annotations</code>** | List of annotations | `{"org.opencontainers.image.created": "2021-09-30T14:00:00Z","org.opencontainers.image.description": "Application image"}` |
190+
| **Parameter** | **Description** | **Example** |
191+
| ---------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
192+
| **<code>name</code>** | Image name | `application` |
193+
| **<code>registry</code>** | Registry where the image is stored | `ghcr.io` |
194+
| **<code>repository</code>** | Repository name | `my-org/my-repo/application` |
195+
| **<code>tags</code>** | List of tags | `["pr-63-5222075","pr-63"]` |
196+
| **<code>images</code>** | List of images | `["ghcr.io/my-org/my-repo/application:pr-63-5222075@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d","ghcr.io/my-org/my-repo/application:pr-63@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d"]` |
197+
| **<code>digest</code>** | | `sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d` |
198+
| **<code>annotations</code>** | List of annotations | `{"org.opencontainers.image.created": "2021-09-30T14:00:00Z","org.opencontainers.image.description": "Application image"}` |
199199

200200
<!-- end outputs -->
201201
<!-- start [.github/ghadocs/examples/] -->

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

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@ on: # yamllint disable-line rule:truthy
1919
"repository": "my-org/my-repo/application",
2020
"tags": ["pr-63-5222075","pr-63"],
2121
"images": [
22-
"ghcr.io/my-org/my-repo/application:pr-63-5222075",
23-
"ghcr.io/my-org/my-repo/application:pr-63"
24-
],
25-
"digests": [
26-
"ghcr.io/my-org/my-repo/application@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d"
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"
2724
],
25+
"digest": "sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
2826
"annotations": {
2927
"org.opencontainers.image.created": "2021-09-30T14:00:00Z",
3028
"org.opencontainers.image.description": "Application image"
@@ -441,7 +439,7 @@ jobs:
441439
needs: [prepare-variables, build-images]
442440
runs-on: ${{ fromJson(inputs.runs-on) }}
443441
outputs:
444-
built-images: ${{ steps.built-images.outputs.built-images }}
442+
built-images: ${{ steps.create-images-manifests.outputs.built-images }}
445443
steps:
446444
- id: get-matrix-outputs
447445
uses: hoverkraft-tech/ci-github-common/actions/get-matrix-outputs@6857ef6d10f704e0998aa4955282f27d1b9be778 # 0.23.1
@@ -463,15 +461,15 @@ jobs:
463461
// Group by image name
464462
const images = {};
465463
builtImages.forEach(builtImage => {
466-
const { name, digests, ...image } = builtImage;
464+
const { name, image, ...rest } = builtImage;
467465
if (!images[name]) {
468466
images[name] = {
469467
name,
470-
digests,
471-
...image,
468+
images: [image],
469+
...rest,
472470
};
473471
} else {
474-
images[name].digests = [...new Set([...images[name].digests, ...digests])];
472+
images[name].images = [...new Set([...images[name].images, image])];
475473
}
476474
});
477475
@@ -504,7 +502,7 @@ jobs:
504502
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
505503
with:
506504
script: |
507-
const builtImagesInput = `${{ steps.built-images.outputs.built-images }}`;
505+
const builtImagesInput = `${{ steps.create-images-manifests.outputs.built-images }}`;
508506
let builtImages = null;
509507
try {
510508
builtImages = JSON.parse(builtImagesInput);

actions/docker/build-image/action.yml

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
---
2-
name: "Build image"
3-
description: "Action to build an image with Docker for a specific platform"
4-
author: Hoverkraft
2+
name: "Docker - Build image"
3+
description: |
4+
Action to build and push a "raw" image with Docker for a specific platform.
5+
This action uses the Docker Buildx plugin to build the image.
6+
It supports caching.
7+
It returns the image digest uri, tags, and annotations, but does not handle it itself.
8+
author: hoverkraft
59
branding:
610
icon: package
7-
color: gray-dark
11+
color: blue
812

913
outputs:
1014
built-image:
@@ -14,17 +18,12 @@ outputs:
1418
"name": "application",
1519
"registry": "ghcr.io",
1620
"repository": "my-org/my-repo/application",
21+
"digest": "sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
22+
"image": "ghcr.io/my-org/my-repo/application@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d",
1723
"tags": [
1824
"pr-63-5222075",
1925
"pr-63"
2026
],
21-
"images": [
22-
"ghcr.io/my-org/my-repo/application:pr-63-5222075",
23-
"ghcr.io/my-org/my-repo/application:pr-63"
24-
],
25-
"digests": [
26-
"ghcr.io/my-org/my-repo/application@sha256:d31aa93410434ac9dcfc9179cac2cb1fd4d7c27f11527addc40299c7c675f49d"
27-
],
2827
"annotations": {
2928
"org.opencontainers.image.created": "2021-09-30T14:00:00Z",
3029
"org.opencontainers.image.description": "Application image"
@@ -257,8 +256,7 @@ runs:
257256
}
258257
259258
if (builtMetadata["containerimage.digest"] === undefined) {
260-
core.setFailed('Given "metadata"."containerimage.digest" output is undefined.');
261-
return;
259+
return core.setFailed('Given "metadata"."containerimage.digest" output is undefined.');
262260
}
263261
264262
const name = `${{ inputs.image }}`;
@@ -273,15 +271,26 @@ runs:
273271
.map(tag => tag.replace(/[^\/]+\/[^:]+:(.+)/,'$1').trim())
274272
.filter(tag => tag !== "");
275273
276-
const images = tags.map(tag => `${image}:${tag}`);
277274
const digests = builtMetadata["containerimage.digest"]
278275
.split(",")
279276
.map(digest => {
280277
const cleanedDigest = digest.trim();
281-
return cleanedDigest !== "" ? `${image}@${cleanedDigest}` : null;
278+
return cleanedDigest !== "" ? cleanedDigest : null;
282279
})
283280
.filter(digest => digest !== null);
284281
282+
const uniqueDigests = [...new Set(digests)];
283+
if (uniqueDigests.length === 0) {
284+
return core.setFailed('No valid digests found in "containerimage.digest" output.');
285+
}
286+
287+
if( uniqueDigests.length > 1 ) {
288+
return core.setFailed(`Multiple digests found: ${uniqueDigests.join(", ")}.`);
289+
}
290+
291+
const digest = uniqueDigests[0];
292+
const imageWithDigest = `${image}@${digest}`;
293+
285294
const annotations = `${{ steps.metadata.outputs.annotations }}`
286295
.split("\n")
287296
.map(annotation => {
@@ -303,8 +312,8 @@ runs:
303312
annotations,
304313
registry,
305314
repository,
306-
images,
307-
digests,
315+
image: imageWithDigest,
316+
digest
308317
};
309318
310319
core.setOutput("built-image", JSON.stringify(builtImage));

0 commit comments

Comments
 (0)