Skip to content

Commit 9053b5e

Browse files
committed
feat(build_container_image): add initial reusable workflow
1 parent cfa105d commit 9053b5e

File tree

5 files changed

+383
-107
lines changed

5 files changed

+383
-107
lines changed
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
name: Build Container Image
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
registry:
7+
description: 'Container registry'
8+
required: true
9+
type: string
10+
name:
11+
description: 'Image name, without registry or tags (repo/name)'
12+
required: true
13+
type: string
14+
tag:
15+
description: 'Tag for manifest creation'
16+
required: true
17+
type: string
18+
build-runners:
19+
description: 'JSON array of runners to build on (e.g. ["ubuntu-24.04", "ubuntu-24.04-arm"])'
20+
required: false
21+
type: string
22+
default: '["ubuntu-24.04", "ubuntu-24.04-arm"]'
23+
merge-runner:
24+
description: 'Runner for merge job'
25+
required: false
26+
type: string
27+
default: 'ubuntu-24.04'
28+
dockerfile:
29+
description: 'Path to Dockerfile'
30+
required: false
31+
type: string
32+
default: 'Dockerfile'
33+
context:
34+
description: 'Build context path'
35+
required: false
36+
type: string
37+
default: '.'
38+
build-args:
39+
description: 'Build arguments as multi-line string (e.g. "ARG1=value1\nARG2=value2")'
40+
required: false
41+
type: string
42+
default: ''
43+
build-args-for-arch:
44+
description: 'Architecture-specific build arguments as JSON object (e.g. {"x86_64": "ARG1=value1\nARG2=value2", "aarch64": "ARG3=value3"})'
45+
required: false
46+
type: string
47+
default: '{}'
48+
target:
49+
description: 'Target stage in multi-stage Dockerfile'
50+
required: false
51+
type: string
52+
default: ''
53+
buildkit-config:
54+
description: 'BuildKit daemon configuration'
55+
required: false
56+
type: string
57+
default: ''
58+
cache-prefix:
59+
description: 'Prefix for cache image names'
60+
required: false
61+
type: string
62+
default: ''
63+
update-cache:
64+
description: 'Enable cache-to for pushing updated cache layers'
65+
required: false
66+
type: boolean
67+
default: false
68+
artifact-name:
69+
description: 'Name of the artifact to be downloaded before build'
70+
required: false
71+
type: string
72+
default: ''
73+
artifact-path:
74+
description: 'Directory where the artifact should be unpacked'
75+
required: false
76+
type: string
77+
default: '.'
78+
checkout-submodules:
79+
description: 'Whether to checkout git submodules'
80+
required: false
81+
type: boolean
82+
default: false
83+
checkout-ref:
84+
description: 'Git ref to checkout'
85+
required: false
86+
type: string
87+
default: ''
88+
checkout-path:
89+
description: 'Path to checkout'
90+
required: false
91+
type: string
92+
default: '.'
93+
94+
outputs:
95+
digest:
96+
description: 'Digest of the multi-arch image'
97+
value: ${{ jobs.merge.outputs.digest }}
98+
ref-with-digest:
99+
description: 'Full image reference with digest'
100+
value: ${{ jobs.merge.outputs.ref-with-digest }}
101+
102+
secrets:
103+
checkout-token:
104+
description: 'GitHub token for checkout'
105+
required: false
106+
registry-user:
107+
required: false
108+
registry-password:
109+
required: false
110+
build-secrets:
111+
description: 'Build secrets as a multi-line string (e.g. "SECRET1=value1\nSECRET2=value2")'
112+
required: false
113+
114+
permissions: {}
115+
116+
defaults:
117+
run:
118+
shell: bash -euo pipefail {0}
119+
120+
jobs:
121+
build-image:
122+
runs-on: ${{ matrix.runner }}
123+
124+
strategy:
125+
fail-fast: false
126+
matrix:
127+
runner: ${{ fromJSON(inputs.build-runners) }}
128+
129+
permissions:
130+
contents: read
131+
packages: write
132+
133+
outputs:
134+
digest_x86_64: ${{ steps.digest.outputs.digest_x86_64 }}
135+
digest_aarch64: ${{ steps.digest.outputs.digest_aarch64 }}
136+
137+
steps:
138+
- name: Checkout
139+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
140+
with:
141+
submodules: ${{ inputs.checkout-submodules }}
142+
ref: ${{ inputs.checkout-ref }}
143+
token: ${{ secrets.checkout-token != '' && secrets.checkout-token || github.token }}
144+
path: ${{ inputs.checkout-path }}
145+
146+
- name: Download artifact
147+
if: ${{ inputs.artifact-name != '' }}
148+
uses: actions/download-artifact@v4
149+
with:
150+
name: ${{ inputs.artifact-name }}
151+
path: ${{ inputs.artifact-path }}
152+
153+
- name: Set up Docker Buildx
154+
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
155+
with:
156+
cache-binary: false
157+
buildkitd-config-inline: ${{ inputs.buildkit-config || '' }}
158+
159+
- name: Login to Container Registry
160+
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
161+
with:
162+
registry: ${{ inputs.registry }}
163+
username: ${{ secrets.registry-user != '' && secrets.registry-user || (inputs.registry == 'ghcr.io' && github.actor) }}
164+
password: ${{ secrets.registry-password != '' && secrets.registry-password || (inputs.registry == 'ghcr.io' && github.token) }}
165+
166+
- name: Detect architecture
167+
id: arch
168+
run: |
169+
arch=$(uname -m)
170+
echo "arch=${arch}" | tee -a "$GITHUB_OUTPUT"
171+
172+
- name: Build and push architecture-specific image
173+
id: build
174+
env:
175+
CACHE_KEY: ${{ inputs.cache-prefix && format('type=registry,ref={0}/{1}:{2}-{3}', inputs.registry, inputs.name, inputs.cache-prefix, steps.arch.outputs.arch) || '' }}
176+
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
177+
with:
178+
context: ${{ inputs.context }}
179+
file: ${{ inputs.dockerfile }}
180+
push: true
181+
pull: true
182+
target: ${{ inputs.target }}
183+
build-args: ${{ format('{0}\n{1}', inputs.build-args, fromJson(inputs.build-args-for-arch)[steps.arch.outputs.arch] || '') }}
184+
secrets: ${{ secrets.build-secrets }}
185+
cache-from: ${{ env.CACHE_KEY }}
186+
cache-to: ${{ inputs.update-cache == 'true' && format('{0},image-manifest=true,oci-mediatypes=true,mode=max', env.CACHE_KEY) || '' }}
187+
attests: |
188+
type=provenance,mode=max
189+
type=sbom,generator=${{ contains(inputs.registry, 'databricks.com') && format('{0}/brickstore/neon/docker/buildkit-syft-scanner:1', inputs.registry) || 'docker.io/docker/buildkit-syft-scanner:1' }}
190+
outputs: type=registry,name=${{ inputs.registry }}/${{ inputs.name }},push-by-digest=true,name-canonical=true
191+
192+
- name: Export digest for architecture
193+
id: digest
194+
run: |
195+
digest="${{ steps.build.outputs.digest }}"
196+
echo "digest_$(uname -m)=${digest}" | tee -a "$GITHUB_OUTPUT"
197+
198+
merge:
199+
runs-on: ${{ inputs.merge-runner }}
200+
needs: [build-image]
201+
if: always() && !cancelled() && !failure()
202+
203+
permissions:
204+
contents: read
205+
packages: write
206+
207+
env:
208+
IMAGE_REF: ${{ inputs.registry }}/${{ inputs.name }}
209+
210+
outputs:
211+
digest: ${{ steps.merge.outputs.digest }}
212+
ref-with-digest: ${{ steps.merge.outputs.ref-with-digest }}
213+
214+
steps:
215+
- name: Checkout
216+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
217+
218+
- name: Set up Docker Buildx
219+
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
220+
221+
- name: Login to Container Registry
222+
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
223+
with:
224+
registry: ${{ inputs.registry }}
225+
username: ${{ secrets.registry-user != '' && secrets.registry-user || (inputs.registry == 'ghcr.io' && github.actor) }}
226+
password: ${{ secrets.registry-password != '' && secrets.registry-password || (inputs.registry == 'ghcr.io' && github.token) }}
227+
228+
- name: Prepare references
229+
id: prepare
230+
env:
231+
DIGEST_X86_64: ${{ needs.build-image.outputs.digest_x86_64 }}
232+
DIGEST_AARCH64: ${{ needs.build-image.outputs.digest_aarch64 }}
233+
run: |
234+
# Parse environment variables and create references in one go
235+
references_data=$(printenv | jq -Rsc '[split("\n")[] | capture("^DIGEST_(?<arch>[^=]+)=(?<digest>.+)$") | .arch |= ascii_downcase]')
236+
237+
# Verify we have at least one digest
238+
if [[ "$(echo "$references_data" | jq -r 'length')" -eq 0 ]]; then
239+
echo "::error::No digest values found! Cannot create manifest list without at least one input reference."
240+
exit 1
241+
fi
242+
243+
# Log found architectures and their digests
244+
echo "$references_data" | jq -r '.[] | "Found digest for \(.arch): \(.digest)"'
245+
246+
# Create space-separated references string for the composite action
247+
references_string=$(echo "$references_data" | jq -r --arg ref "$IMAGE_REF" 'map($ref + "@" + .digest) | join(" ")')
248+
echo "references=${references_string}" >> "$GITHUB_OUTPUT"
249+
250+
- name: Merge OCI manifest lists
251+
id: merge
252+
uses: ./merge-oci-manifest-lists
253+
with:
254+
target: ${{ inputs.registry }}/${{ inputs.name }}:${{ inputs.tag }}
255+
references: ${{ steps.prepare.outputs.references }}
256+
257+
- name: Fetch manifest digest references
258+
id: digests
259+
uses: ./fetch-oci-manifest-list-digest-references

.github/workflows/mutexbot-cleanup.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on:
44
pull_request:
55
types: ["closed"]
66
branches: ["main"]
7-
paths: ["mutexbot/**"]
7+
paths: ["mutexbot/**", ".github/workflows/mutexbot*.yml"]
88

99
permissions: {}
1010

0 commit comments

Comments
 (0)