Skip to content

Commit 75e8d89

Browse files
authored
chore: add support for multiple releases and pr image builds (#35)
* chore: update release tag logic and move build logic into reusable workflow for PR builds Assisted-By: Cursor Signed-off-by: Frank Kong <frkong@redhat.com> * chore: update renovate to perform major gh action version updates Signed-off-by: Frank Kong <frkong@redhat.com> * chore: add pull request builds Assisted-By: Cursor Signed-off-by: Frank Kong <frkong@redhat.com> * chore: update to handle multi-arch builds when not pushing immediately Assisted-By: Cursor Signed-off-by: Frank Kong <frkong@redhat.com> * chore: fix image override issue Assisted-By: Cursor Signed-off-by: Frank Kong <frkong@redhat.com> * chore: switch to quay api for cleanup Signed-off-by: Frank Kong <frkong@redhat.com> * chore: switch to oauth token to delete arch tags Signed-off-by: Frank Kong <frkong@redhat.com> * chore: update summary and comment with an actual expiration date Signed-off-by: Frank Kong <frkong@redhat.com> * chore: update pr-publish to also comment after failure Signed-off-by: Frank Kong <frkong@redhat.com> * chore: update the commit hash the pr commit hash Signed-off-by: Frank Kong <frkong@redhat.com> * chore: add comment when build fails Signed-off-by: Frank Kong <frkong@redhat.com> * chore: add pull_request trigger conditions Signed-off-by: Frank Kong <frkong@redhat.com> * chore: add tag validation and skopeo error surfacing Signed-off-by: Frank Kong <frkong@redhat.com> --------- Signed-off-by: Frank Kong <frkong@redhat.com>
1 parent b11f79a commit 75e8d89

7 files changed

Lines changed: 1131 additions & 208 deletions

File tree

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
name: 'Build Container'
2+
description: 'Reusable action for building container images with support for both artifact export and registry push'
3+
4+
inputs:
5+
registry:
6+
description: 'Container registry URL (e.g., quay.io)'
7+
required: true
8+
image:
9+
description: 'Full image name including namespace (e.g., my-org/my-image)'
10+
required: true
11+
registry-username:
12+
description: 'Registry username for login (required when push is true)'
13+
required: false
14+
registry-password:
15+
description: 'Registry password/token for login (required when push is true)'
16+
required: false
17+
push:
18+
description: 'Whether to push the image to registry. If false, exports as artifact instead.'
19+
required: false
20+
default: 'true'
21+
pr-number:
22+
description: 'PR number - if provided, generates PR-style tags (pr-X, pr-X-sha)'
23+
required: false
24+
commit-sha:
25+
description: 'Full commit SHA'
26+
required: true
27+
version:
28+
description: 'Version string (e.g., 1.8) - used for release-style tags'
29+
required: false
30+
release-number:
31+
description: 'Release number (e.g., 26) - used for release-style tags'
32+
required: false
33+
tag-latest:
34+
description: 'Whether to also tag as latest (only applies to release-style tags)'
35+
required: false
36+
default: 'false'
37+
platforms:
38+
description: 'Platforms to build for'
39+
required: false
40+
default: 'linux/amd64,linux/arm64'
41+
expiration:
42+
description: 'Tag expiration label for PR images (e.g., 2w)'
43+
required: false
44+
default: '2w'
45+
46+
outputs:
47+
primary-tag:
48+
description: 'The primary tag for the built image'
49+
value: ${{ steps.tags.outputs.primary_tag }}
50+
primary-image:
51+
description: 'Full primary image reference'
52+
value: ${{ steps.tags.outputs.primary_image }}
53+
all-tags:
54+
description: 'All tags as comma-separated list'
55+
value: ${{ steps.tags.outputs.all_tags }}
56+
short-sha:
57+
description: 'Short commit SHA'
58+
value: ${{ steps.tags.outputs.short_sha }}
59+
artifact-name:
60+
description: 'Name of the uploaded artifact (when push is false)'
61+
value: ${{ steps.export.outputs.artifact_name }}
62+
platform:
63+
description: 'Platform that was built (when push is false)'
64+
value: ${{ steps.export.outputs.platform }}
65+
arch:
66+
description: 'Architecture that was built (when push is false)'
67+
value: ${{ steps.export.outputs.arch }}
68+
69+
runs:
70+
using: 'composite'
71+
steps:
72+
- name: Validate inputs
73+
shell: bash
74+
env:
75+
PUSH: ${{ inputs.push }}
76+
PR_NUMBER: ${{ inputs.pr-number }}
77+
VERSION: ${{ inputs.version }}
78+
RELEASE_NUMBER: ${{ inputs.release-number }}
79+
run: |
80+
# Must provide either pr-number OR version+release-number
81+
if [[ -n "$PR_NUMBER" && -n "$VERSION" ]]; then
82+
echo "Error: Cannot provide both pr-number and version. Choose one tagging style."
83+
exit 1
84+
fi
85+
86+
if [[ -z "$PR_NUMBER" && -z "$VERSION" ]]; then
87+
echo "Error: Must provide either pr-number (for PR builds) or version (for release builds)"
88+
exit 1
89+
fi
90+
91+
if [[ -n "$VERSION" && -z "$RELEASE_NUMBER" ]]; then
92+
echo "Error: release-number is required when version is provided"
93+
exit 1
94+
fi
95+
96+
- name: Prepare tags
97+
id: tags
98+
shell: bash
99+
env:
100+
REGISTRY: ${{ inputs.registry }}
101+
IMAGE: ${{ inputs.image }}
102+
PR_NUMBER: ${{ inputs.pr-number }}
103+
COMMIT_SHA: ${{ inputs.commit-sha }}
104+
VERSION: ${{ inputs.version }}
105+
RELEASE_NUMBER: ${{ inputs.release-number }}
106+
TAG_LATEST: ${{ inputs.tag-latest }}
107+
run: |
108+
SHORT_SHA="${COMMIT_SHA:0:7}"
109+
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
110+
111+
if [[ -n "$PR_NUMBER" ]]; then
112+
# Tags for PR builds
113+
PRIMARY_TAG="pr-${PR_NUMBER}"
114+
ALL_TAGS="${PRIMARY_TAG},pr-${PR_NUMBER}-${SHORT_SHA}"
115+
echo "Building with PR tags: ${ALL_TAGS}"
116+
else
117+
# Tags for release builds
118+
PRIMARY_TAG="${VERSION}-${RELEASE_NUMBER}"
119+
ALL_TAGS="${PRIMARY_TAG},${VERSION},${SHORT_SHA}"
120+
if [[ "$TAG_LATEST" == "true" ]]; then
121+
ALL_TAGS="${ALL_TAGS},latest"
122+
fi
123+
echo "Building with release tags: ${ALL_TAGS}"
124+
fi
125+
126+
echo "primary_tag=${PRIMARY_TAG}" >> $GITHUB_OUTPUT
127+
echo "primary_image=${REGISTRY}/${IMAGE}:${PRIMARY_TAG}" >> $GITHUB_OUTPUT
128+
echo "all_tags=${ALL_TAGS}" >> $GITHUB_OUTPUT
129+
130+
- name: Set up QEMU
131+
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3.7.0
132+
133+
- name: Set up Docker Buildx
134+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
135+
136+
- name: Login to Registry
137+
if: inputs.push == 'true' && inputs.registry-username != '' && inputs.registry-password != ''
138+
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
139+
with:
140+
registry: ${{ inputs.registry }}
141+
username: ${{ inputs.registry-username }}
142+
password: ${{ inputs.registry-password }}
143+
144+
- name: Generate Docker tags
145+
id: docker-tags
146+
shell: bash
147+
env:
148+
REGISTRY: ${{ inputs.registry }}
149+
IMAGE: ${{ inputs.image }}
150+
ALL_TAGS: ${{ steps.tags.outputs.all_tags }}
151+
PUSH: ${{ inputs.push }}
152+
PLATFORMS: ${{ inputs.platforms }}
153+
run: |
154+
ARCH=""
155+
if [[ "$PUSH" != "true" ]]; then
156+
if ! [[ "$PLATFORMS" =~ ^[a-z]+/[a-z0-9]+$ ]]; then
157+
echo "Error: Invalid platform format: $PLATFORMS"
158+
exit 1
159+
fi
160+
ARCH="${PLATFORMS#*/}"
161+
echo "Export mode: adding architecture suffix '-${ARCH}' to tags"
162+
fi
163+
164+
# Convert comma-separated tags to newline-separated full image references
165+
DOCKER_TAGS=""
166+
IFS=',' read -ra TAG_ARRAY <<< "$ALL_TAGS"
167+
for tag in "${TAG_ARRAY[@]}"; do
168+
if [[ -n "$DOCKER_TAGS" ]]; then
169+
DOCKER_TAGS="${DOCKER_TAGS}"$'\n'
170+
fi
171+
if [[ -n "$ARCH" ]]; then
172+
DOCKER_TAGS="${DOCKER_TAGS}${REGISTRY}/${IMAGE}:${tag}-${ARCH}"
173+
else
174+
DOCKER_TAGS="${DOCKER_TAGS}${REGISTRY}/${IMAGE}:${tag}"
175+
fi
176+
done
177+
178+
{
179+
echo "tags<<EOF"
180+
echo "$DOCKER_TAGS"
181+
echo "EOF"
182+
} >> $GITHUB_OUTPUT
183+
184+
- name: Determine labels
185+
id: labels
186+
shell: bash
187+
env:
188+
PR_NUMBER: ${{ inputs.pr-number }}
189+
EXPIRATION: ${{ inputs.expiration }}
190+
run: |
191+
if [[ -n "$PR_NUMBER" ]]; then
192+
echo "labels=quay.expires-after=${EXPIRATION}" >> $GITHUB_OUTPUT
193+
else
194+
echo "labels=" >> $GITHUB_OUTPUT
195+
fi
196+
197+
- name: Build and Push
198+
if: inputs.push == 'true'
199+
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
200+
with:
201+
context: .
202+
platforms: ${{ inputs.platforms }}
203+
push: true
204+
tags: ${{ steps.docker-tags.outputs.tags }}
205+
labels: ${{ steps.labels.outputs.labels }}
206+
cache-from: type=gha
207+
cache-to: type=gha,mode=max
208+
209+
- name: Build and Export
210+
if: inputs.push != 'true'
211+
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
212+
with:
213+
context: .
214+
# When exporting as artifact, only single platform is supported
215+
# Use the first platform from the input (caller should pass single platform)
216+
platforms: ${{ inputs.platforms }}
217+
push: false
218+
tags: ${{ steps.docker-tags.outputs.tags }}
219+
labels: ${{ steps.labels.outputs.labels }}
220+
outputs: type=docker,dest=/tmp/container-image.tar
221+
cache-from: type=gha
222+
cache-to: type=gha,mode=max
223+
224+
- name: Prepare artifact name
225+
if: inputs.push != 'true'
226+
id: export
227+
shell: bash
228+
env:
229+
PR_NUMBER: ${{ inputs.pr-number }}
230+
VERSION: ${{ inputs.version }}
231+
RELEASE_NUMBER: ${{ inputs.release-number }}
232+
PLATFORMS: ${{ inputs.platforms }}
233+
run: |
234+
# Validate platform format (must be os/arch, e.g., linux/amd64)
235+
if ! [[ "$PLATFORMS" =~ ^[a-z]+/[a-z0-9]+$ ]]; then
236+
echo "Error: Invalid platform format: $PLATFORMS"
237+
echo "Expected format: os/arch (e.g., linux/amd64, linux/arm64)"
238+
exit 1
239+
fi
240+
241+
# Extract architecture from platform (e.g., linux/amd64 -> amd64)
242+
ARCH="${PLATFORMS#*/}"
243+
244+
if ! [[ "$ARCH" =~ ^(amd64|arm64)$ ]]; then
245+
echo "Error: Unsupported architecture: $ARCH"
246+
exit 1
247+
fi
248+
249+
if [[ -n "$PR_NUMBER" ]]; then
250+
ARTIFACT_NAME="container-image-pr-${PR_NUMBER}-${ARCH}"
251+
else
252+
ARTIFACT_NAME="container-image-${VERSION}-${RELEASE_NUMBER}-${ARCH}"
253+
fi
254+
echo "artifact_name=${ARTIFACT_NAME}" >> $GITHUB_OUTPUT
255+
echo "platform=${PLATFORMS}" >> $GITHUB_OUTPUT
256+
echo "arch=${ARCH}" >> $GITHUB_OUTPUT
257+
258+
- name: Upload container artifact
259+
if: inputs.push != 'true'
260+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
261+
with:
262+
name: ${{ steps.export.outputs.artifact_name }}
263+
path: /tmp/container-image.tar
264+
retention-days: 7
265+
if-no-files-found: error

0 commit comments

Comments
 (0)