Skip to content

Commit fdab30a

Browse files
committed
chore: split-off image name sanitization
1 parent 00e94b1 commit fdab30a

File tree

4 files changed

+118
-73
lines changed

4 files changed

+118
-73
lines changed

.github/workflows/wc-build-push-test.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,7 @@ jobs:
4242
needs: build-push
4343
uses: ./.github/workflows/wc-integration-test.yml
4444
with:
45-
fully-qualified-image-name: ${{ needs.build-push.outputs.fully-qualified-image-name }}
46-
image-basename: ${{ needs.build-push.outputs.image-basename }}
45+
image-name: ${{ github.repository }}-${{ matrix.flavor }}
4746
test-file: test/${{ matrix.flavor }}/integration.bats
4847
runner-labels: ${{ matrix.runner }}
4948

.github/workflows/wc-build-push.yml

Lines changed: 24 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,6 @@ on:
3434
required: false
3535
type: string
3636
default: ubuntu-latest
37-
outputs:
38-
image-basename:
39-
description: "The sanitized base name of the image (without registry or tag)"
40-
value: ${{ jobs.sanitize-inputs.outputs.image-basename }}
41-
image-name:
42-
description: "The sanitized name of the image (without registry or tag)"
43-
value: ${{ jobs.sanitize-inputs.outputs.image-name }}
44-
fully-qualified-image-name:
45-
description: "The fully qualified name of the image including registry (but without tag)"
46-
value: ${{ jobs.sanitize-inputs.outputs.fully-qualified-image-name }}
4737
secrets:
4838
DOCKER_USERNAME:
4939
description: "User name for Docker login, if not provided the GitHub actor will be used"
@@ -55,44 +45,18 @@ on:
5545
permissions: {}
5646

5747
jobs:
58-
sanitize-inputs:
59-
runs-on: ${{ inputs.default-runner }}
60-
outputs:
61-
image-basename: ${{ steps.sanitize-image-name.outputs.sanitized-basename }}
62-
image-name: ${{ steps.sanitize-image-name.outputs.sanitized-image-name }}
63-
fully-qualified-image-name: ${{ inputs.registry }}/${{ steps.sanitize-image-name.outputs.sanitized-image-name }}
64-
steps:
65-
- name: Sanitize image name
66-
id: sanitize-image-name
67-
env:
68-
IMAGE_NAME: ${{ inputs.image-name }}
69-
run: |
70-
set -Eeuo pipefail
71-
72-
# Split all image name components (on '/') and sanitize each component independently.
73-
# Rules: lowercase; allowed chars a-z0-9._- ; collapse invalid sequences to single '-'; trim leading/trailing '-'.
74-
IFS='/' read -r -a PARTS <<< "$IMAGE_NAME"
75-
SANITIZED_PARTS=()
76-
77-
for PART in "${PARTS[@]}"; do
78-
SANITIZED_PART=$(echo "$PART" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9._-]+/-/g' | sed -E 's/^-+|-+$//g')
79-
if [ -z "$SANITIZED_PART" ]; then
80-
echo "Invalid or empty component after sanitization in image component: '$PART', please correct your image name: '$IMAGE_NAME'" >&2
81-
exit 1
82-
fi
83-
SANITIZED_PARTS+=("$SANITIZED_PART")
84-
done
85-
86-
SANITIZED_IMAGE_NAME=$(IFS='/'; echo "${SANITIZED_PARTS[*]}")
87-
SANITIZED_BASENAME=${SANITIZED_PARTS[-1]}
88-
echo "sanitized-image-name=$SANITIZED_IMAGE_NAME" >> "$GITHUB_OUTPUT"
89-
echo "sanitized-basename=$SANITIZED_BASENAME" >> "$GITHUB_OUTPUT"
48+
sanitize-image-name:
49+
uses: ./.github/workflows/wc-sanitize-image-name.yml
50+
with:
51+
image-name: ${{ inputs.image-name }}
52+
registry: ${{ inputs.registry }}
53+
runner-labels: ${{ inputs.default-runner }}
9054

9155
build-push:
9256
strategy:
9357
matrix: ${{ fromJson(inputs.build-matrix) }}
9458
runs-on: ${{ matrix.runner }}
95-
needs: sanitize-inputs
59+
needs: sanitize-image-name
9660
permissions:
9761
contents: read
9862
packages: write
@@ -118,7 +82,7 @@ jobs:
11882
DOCKER_METADATA_SET_OUTPUT_ENV: false
11983
id: metadata
12084
with:
121-
images: ${{ needs.sanitize-inputs.outputs.fully-qualified-image-name }}
85+
images: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}
12286
# Generate image LABEL for devcontainer.metadata
12387
# the sed expression is a workaround for quotes being eaten in arrays (e.g. ["x", "y", "z"] -> ["x",y,"z"])
12488
- run: echo "metadata=$(jq -cj '[.]' ".devcontainer/${CONTAINER_FLAVOR}/devcontainer-metadata-vscode.json" | sed 's/,"/, "/g')" >> "$GITHUB_OUTPUT"
@@ -134,7 +98,7 @@ jobs:
13498
with:
13599
file: ${{ inputs.dockerfile }}
136100
push: true
137-
tags: ${{ needs.sanitize-inputs.outputs.fully-qualified-image-name }}
101+
tags: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}
138102
labels: |
139103
${{ steps.metadata.outputs.labels }}
140104
devcontainer.metadata=${{ steps.devcontainer-metadata.outputs.metadata }}
@@ -151,7 +115,7 @@ jobs:
151115
RUNNER_TEMP: ${{ runner.temp }}
152116
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
153117
with:
154-
name: digests-${{ needs.sanitize-inputs.outputs.image-basename }}-${{ steps.devcontainer-arch.outputs.arch }}
118+
name: digests-${{ needs.sanitize-image-name.outputs.image-basename }}-${{ steps.devcontainer-arch.outputs.arch }}
155119
path: ${{ runner.temp }}/digests/*
156120
if-no-files-found: error
157121
retention-days: 1
@@ -160,7 +124,7 @@ jobs:
160124
runs-on: ${{ inputs.default-runner }}
161125
needs:
162126
- build-push
163-
- sanitize-inputs
127+
- sanitize-image-name
164128
permissions:
165129
actions: read
166130
attestations: write
@@ -181,7 +145,7 @@ jobs:
181145
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
182146
with:
183147
path: ${{ runner.temp }}/digests
184-
pattern: digests-${{ needs.sanitize-inputs.outputs.image-basename }}-*
148+
pattern: digests-${{ needs.sanitize-image-name.outputs.image-basename }}-*
185149
merge-multiple: true
186150
- uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
187151
- uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
@@ -198,7 +162,7 @@ jobs:
198162
DOCKER_METADATA_ANNOTATIONS_LEVELS: index
199163
DOCKER_METADATA_SET_OUTPUT_ENV: false
200164
with:
201-
images: ${{ needs.sanitize-inputs.outputs.fully-qualified-image-name }}
165+
images: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}
202166
# Generate Docker tags based on the following events/attributes.
203167
# To prevent unnecessary image builds we simulate the `type=edge` tag
204168
# with `type=raw,value=edge,enable=...` which only enables the tag
@@ -230,7 +194,7 @@ jobs:
230194
print(' '.join(command))
231195
subprocess.run(command, check=True)
232196
env:
233-
FULLY_QUALIFIED_IMAGE_NAME: ${{ needs.sanitize-inputs.outputs.fully-qualified-image-name }}
197+
FULLY_QUALIFIED_IMAGE_NAME: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}
234198
METADATA_JSON: ${{ steps.metadata.outputs.json }}
235199
shell: python
236200
working-directory: ${{ runner.temp }}/digests
@@ -241,44 +205,44 @@ jobs:
241205
output=$(docker buildx imagetools inspect "${CONTAINER}" --format '{{json .}}')
242206
echo "digest=$(echo "$output" | jq -r '.manifest.digest // .manifests[0].digest')" >> "$GITHUB_OUTPUT"
243207
env:
244-
CONTAINER: ${{ needs.sanitize-inputs.outputs.fully-qualified-image-name }}:${{ steps.metadata.outputs.version }}
208+
CONTAINER: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}:${{ steps.metadata.outputs.version }}
245209
- run: |
246210
set -Eeuo pipefail
247211
wget -O diffoci https://github.com/reproducible-containers/diffoci/releases/download/v0.1.7/diffoci-v0.1.7.linux-amd64
248212
chmod +x diffoci
249213
./diffoci diff --semantic --report-file=container-diff.json "${FROM_CONTAINER}" "${TO_CONTAINER}" || true
250214
env:
251-
FROM_CONTAINER: ${{ needs.sanitize-inputs.outputs.fully-qualified-image-name }}:edge
252-
TO_CONTAINER: ${{ needs.sanitize-inputs.outputs.fully-qualified-image-name }}:${{ steps.metadata.outputs.version }}
215+
FROM_CONTAINER: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}:edge
216+
TO_CONTAINER: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}:${{ steps.metadata.outputs.version }}
253217
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
254218
with:
255-
name: container-diff-${{ needs.sanitize-inputs.outputs.image-basename }}
219+
name: container-diff-${{ needs.sanitize-image-name.outputs.image-basename }}
256220
path: container-diff.json
257221
retention-days: 10
258222
- uses: ./.github/actions/container-size-diff
259223
id: container-size-diff
260224
with:
261-
from-container: ${{ needs.sanitize-inputs.outputs.fully-qualified-image-name }}:edge
262-
to-container: ${{ needs.sanitize-inputs.outputs.fully-qualified-image-name }}:${{ steps.metadata.outputs.version }}
225+
from-container: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}:edge
226+
to-container: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}:${{ steps.metadata.outputs.version }}
263227
- uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
264228
with:
265-
header: container-size-diff-${{ needs.sanitize-inputs.outputs.image-basename }}
229+
header: container-size-diff-${{ needs.sanitize-image-name.outputs.image-basename }}
266230
message: |
267231
${{ steps.container-size-diff.outputs.size-diff-markdown }}
268232
- uses: anchore/sbom-action@f8bdd1d8ac5e901a77a92f111440fdb1b593736b # v0.20.6
269233
with:
270-
image: ${{ needs.sanitize-inputs.outputs.fully-qualified-image-name }}@${{ steps.inspect-manifest.outputs.digest }}
234+
image: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}@${{ steps.inspect-manifest.outputs.digest }}
271235
dependency-snapshot: true
272236
- uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
273237
with:
274-
subject-name: ${{ needs.sanitize-inputs.outputs.fully-qualified-image-name }}
238+
subject-name: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}
275239
subject-digest: ${{ steps.inspect-manifest.outputs.digest }}
276240
show-summary: false
277241
push-to-registry: true
278242
- name: Verify attestation
279243
run: gh attestation verify --repo "${GH_REPO}" "oci://${FULLY_QUALIFIED_IMAGE_NAME}@${DIGEST}"
280244
env:
281245
DIGEST: ${{ steps.inspect-manifest.outputs.digest }}
282-
FULLY_QUALIFIED_IMAGE_NAME: ${{ needs.sanitize-inputs.outputs.fully-qualified-image-name }}
246+
FULLY_QUALIFIED_IMAGE_NAME: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}
283247
GH_REPO: ${{ github.repository }}
284248
GH_TOKEN: ${{ github.token }}

.github/workflows/wc-integration-test.yml

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@ name: Integration Test
44
on:
55
workflow_call:
66
inputs:
7-
fully-qualified-image-name:
8-
required: false
9-
type: string
10-
image-basename:
7+
image-name:
118
required: true
129
type: string
1310
test-file:
@@ -17,12 +14,25 @@ on:
1714
description: "Runner to use for the job, will be passed to `runs-on`"
1815
required: true
1916
type: string
17+
registry:
18+
description: "Docker registry to push built containers to, DOCKER_USERNAME and DOCKER_PASSWORD secrets must be set if not using GitHub Container Registry"
19+
required: false
20+
type: string
21+
default: "ghcr.io"
2022

2123
permissions: {}
2224

2325
jobs:
26+
sanitize-image-name:
27+
uses: ./.github/workflows/wc-sanitize-image-name.yml
28+
with:
29+
image-name: ${{ inputs.image-name }}
30+
registry: ${{ inputs.registry }}
31+
runner-labels: ${{ inputs.runner-labels }}
32+
2433
determine-container:
2534
runs-on: ${{ inputs.runner-labels }}
35+
needs: sanitize-image-name
2636
outputs:
2737
container: ${{ steps.set-container.outputs.container }}
2838
runner-arch: ${{ steps.runner-arch.outputs.arch }}
@@ -35,17 +45,19 @@ jobs:
3545
id: runner-arch
3646
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
3747
with:
38-
path: ${{ runner.temp }}/digests-${{ inputs.image-basename }}-${{ steps.runner-arch.outputs.arch }}
39-
pattern: digests-${{ inputs.image-basename }}-${{ steps.runner-arch.outputs.arch }}
48+
path: ${{ runner.temp }}/digests-${{ needs.sanitize-image-name.outputs.image-basename }}-${{ steps.runner-arch.outputs.arch }}
49+
pattern: digests-${{ needs.sanitize-image-name.outputs.image-basename }}-${{ steps.runner-arch.outputs.arch }}
4050
- run: echo "container=$(printf "${FULLY_QUALIFIED_IMAGE_NAME}@sha256:%s " *)" >> "$GITHUB_OUTPUT"
41-
working-directory: ${{ runner.temp }}/digests-${{ inputs.image-basename }}-${{ steps.runner-arch.outputs.arch }}
51+
working-directory: ${{ runner.temp }}/digests-${{ needs.sanitize-image-name.outputs.image-basename }}-${{ steps.runner-arch.outputs.arch }}
4252
env:
43-
FULLY_QUALIFIED_IMAGE_NAME: ${{ inputs.fully-qualified-image-name }}
53+
FULLY_QUALIFIED_IMAGE_NAME: ${{ needs.sanitize-image-name.outputs.fully-qualified-image-name }}
4454
GH_REPO: ${{ github.repository }}
4555
id: set-container
4656

4757
run-test:
48-
needs: determine-container
58+
needs:
59+
- determine-container
60+
- sanitize-image-name
4961
runs-on: ${{ inputs.runner-labels }}
5062
container: ${{ needs.determine-container.outputs.container }}
5163
permissions:
@@ -66,11 +78,11 @@ jobs:
6678
xwin-cache-${{ needs.determine-container.outputs.runner-arch }}
6779
- run: bats --formatter junit "${TEST_FILE}" | tee "test-report-${IMAGE_BASENAME}-${RUNNER_ARCH}.xml"
6880
env:
69-
IMAGE_BASENAME: ${{ inputs.image-basename }}
81+
IMAGE_BASENAME: ${{ needs.sanitize-image-name.outputs.image-basename }}
7082
TEST_FILE: ${{ inputs.test-file }}
7183
RUNNER_ARCH: ${{ needs.determine-container.outputs.runner-arch }}
7284
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
7385
if: always()
7486
with:
75-
name: test-results-integration-${{ inputs.image-basename }}-${{ needs.determine-container.outputs.runner-arch }}
87+
name: test-results-integration-${{ needs.sanitize-image-name.outputs.image-basename }}-${{ needs.determine-container.outputs.runner-arch }}
7688
path: test-report-*.xml
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
---
2+
name: Sanitize Image Name
3+
4+
on:
5+
workflow_call:
6+
inputs:
7+
image-name:
8+
description: "Name of the Docker image to build, without registry or tag. E.g. 'my-image' or 'my-org/my-image'"
9+
required: true
10+
type: string
11+
registry:
12+
description: "Container registry to push the image to, e.g. ghcr.io"
13+
required: false
14+
type: string
15+
default: "ghcr.io"
16+
runner-labels:
17+
description: "Runner to use for the job, will be passed to `runs-on`"
18+
required: false
19+
type: string
20+
default: "ubuntu-latest"
21+
outputs:
22+
image-basename:
23+
description: "The sanitized base name of the image (without registry or tag)"
24+
value: ${{ jobs.sanitize-inputs.outputs.image-basename }}
25+
image-name:
26+
description: "The sanitized name of the image (without registry or tag)"
27+
value: ${{ jobs.sanitize-inputs.outputs.image-name }}
28+
fully-qualified-image-name:
29+
description: "The fully qualified name of the image including registry (but without tag)"
30+
value: ${{ jobs.sanitize-inputs.outputs.fully-qualified-image-name }}
31+
32+
permissions: {}
33+
34+
jobs:
35+
sanitize-inputs:
36+
runs-on: ${{ inputs.runner-labels }}
37+
outputs:
38+
image-basename: ${{ steps.sanitize-image-name.outputs.sanitized-basename }}
39+
image-name: ${{ steps.sanitize-image-name.outputs.sanitized-image-name }}
40+
fully-qualified-image-name: ${{ inputs.registry }}/${{ steps.sanitize-image-name.outputs.sanitized-image-name }}
41+
steps:
42+
- uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1
43+
with:
44+
disable-sudo: true
45+
egress-policy: audit
46+
- name: Sanitize image name
47+
id: sanitize-image-name
48+
env:
49+
IMAGE_NAME: ${{ inputs.image-name }}
50+
run: |
51+
set -Eeuo pipefail
52+
53+
# Split all image name components (on '/') and sanitize each component independently.
54+
# Rules: lowercase; allowed chars a-z0-9._- ; collapse invalid sequences to single '-'; trim leading/trailing '-'.
55+
IFS='/' read -r -a PARTS <<< "$IMAGE_NAME"
56+
SANITIZED_PARTS=()
57+
58+
for PART in "${PARTS[@]}"; do
59+
SANITIZED_PART=$(echo "$PART" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9._-]+/-/g' | sed -E 's/^-+|-+$//g')
60+
if [ -z "$SANITIZED_PART" ]; then
61+
echo "Invalid or empty component after sanitization in image component: '$PART', please correct your image name: '$IMAGE_NAME'" >&2
62+
exit 1
63+
fi
64+
SANITIZED_PARTS+=("$SANITIZED_PART")
65+
done
66+
67+
SANITIZED_IMAGE_NAME=$(IFS='/'; echo "${SANITIZED_PARTS[*]}")
68+
SANITIZED_BASENAME=${SANITIZED_PARTS[-1]}
69+
echo "sanitized-image-name=$SANITIZED_IMAGE_NAME" >> "$GITHUB_OUTPUT"
70+
echo "sanitized-basename=$SANITIZED_BASENAME" >> "$GITHUB_OUTPUT"

0 commit comments

Comments
 (0)