Skip to content

Commit fe7046d

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

File tree

5 files changed

+381
-107
lines changed

5 files changed

+381
-107
lines changed
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
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-token:
89+
description: 'GitHub token for checkout'
90+
required: false
91+
type: string
92+
default: ''
93+
checkout-path:
94+
description: 'Path to checkout'
95+
required: false
96+
type: string
97+
default: '.'
98+
99+
outputs:
100+
digest:
101+
description: 'Digest of the multi-arch image'
102+
value: ${{ jobs.merge.outputs.digest }}
103+
ref-with-digest:
104+
description: 'Full image reference with digest'
105+
value: ${{ jobs.merge.outputs.ref-with-digest }}
106+
107+
secrets:
108+
registry-user:
109+
required: false
110+
registry-password:
111+
required: false
112+
build-secrets:
113+
description: 'Build secrets as a multi-line string (e.g. "SECRET1=value1\nSECRET2=value2")'
114+
required: false
115+
116+
permissions: {}
117+
118+
defaults:
119+
run:
120+
shell: bash -euo pipefail {0}
121+
122+
jobs:
123+
build-image:
124+
runs-on: ${{ matrix.runner }}
125+
126+
strategy:
127+
fail-fast: false
128+
matrix:
129+
runner: ${{ fromJSON(inputs.build-runners) }}
130+
131+
permissions:
132+
contents: read
133+
packages: write
134+
135+
outputs:
136+
digest_x86_64: ${{ steps.digest.outputs.digest_x86_64 }}
137+
digest_aarch64: ${{ steps.digest.outputs.digest_aarch64 }}
138+
139+
steps:
140+
- name: Checkout
141+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
142+
with:
143+
submodules: ${{ inputs.checkout-submodules }}
144+
ref: ${{ inputs.checkout-ref }}
145+
token: ${{ inputs.checkout-token || github.token }}
146+
path: ${{ inputs.checkout-path }}
147+
148+
- name: Download artifact
149+
if: ${{ inputs.artifact-name != '' }}
150+
uses: actions/download-artifact@v4
151+
with:
152+
name: ${{ inputs.artifact-name }}
153+
path: ${{ inputs.artifact-path }}
154+
155+
- name: Set up Docker Buildx
156+
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
157+
with:
158+
cache-binary: false
159+
buildkitd-config-inline: ${{ inputs.buildkit-config || '' }}
160+
161+
- name: Login to Container Registry
162+
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
163+
with:
164+
registry: ${{ inputs.registry }}
165+
username: ${{ secrets.registry-user != '' && secrets.registry-user || (inputs.registry == 'ghcr.io' && github.actor) }}
166+
password: ${{ secrets.registry-password != '' && secrets.registry-password || (inputs.registry == 'ghcr.io' && github.token) }}
167+
168+
- name: Detect architecture
169+
id: arch
170+
run: |
171+
arch=$(uname -m)
172+
echo "arch=${arch}" | tee -a "$GITHUB_OUTPUT"
173+
174+
- name: Build and push architecture-specific image
175+
id: build
176+
env:
177+
CACHE_KEY: ${{ inputs.cache-prefix && format('type=registry,ref={0}/{1}:{2}-{3}', inputs.registry, inputs.name, inputs.cache-prefix, steps.arch.outputs.arch) || '' }}
178+
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
179+
with:
180+
context: ${{ inputs.context }}
181+
file: ${{ inputs.dockerfile }}
182+
push: true
183+
pull: true
184+
target: ${{ inputs.target }}
185+
build-args: ${{ format('{0}\n{1}', inputs.build-args, fromJson(inputs.build-args-for-arch)[steps.arch.outputs.arch] || '') }}
186+
secrets: ${{ secrets.build-secrets }}
187+
cache-from: ${{ env.CACHE_KEY }}
188+
cache-to: ${{ inputs.update-cache == 'true' && format('{0},image-manifest=true,oci-mediatypes=true,mode=max', env.CACHE_KEY) || '' }}
189+
attests: |
190+
type=provenance,mode=max
191+
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' }}
192+
outputs: type=registry,name=${{ inputs.registry }}/${{ inputs.name }},push-by-digest=true,name-canonical=true
193+
194+
- name: Export digest for architecture
195+
id: digest
196+
run: |
197+
digest="${{ steps.build.outputs.digest }}"
198+
echo "digest_$(uname -m)=${digest}" | tee -a "$GITHUB_OUTPUT"
199+
200+
merge:
201+
runs-on: ${{ inputs.merge-runner }}
202+
needs: [build-image]
203+
if: always() && !cancelled() && !failure()
204+
205+
permissions:
206+
contents: read
207+
packages: write
208+
209+
env:
210+
IMAGE_REF: ${{ inputs.registry }}/${{ inputs.name }}
211+
212+
outputs:
213+
digest: ${{ steps.merge.outputs.digest }}
214+
ref-with-digest: ${{ steps.merge.outputs.ref-with-digest }}
215+
216+
steps:
217+
- name: Checkout
218+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
219+
220+
- name: Set up Docker Buildx
221+
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
222+
223+
- name: Login to Container Registry
224+
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
225+
with:
226+
registry: ${{ inputs.registry }}
227+
username: ${{ secrets.registry-user != '' && secrets.registry-user || (inputs.registry == 'ghcr.io' && github.actor) }}
228+
password: ${{ secrets.registry-password != '' && secrets.registry-password || (inputs.registry == 'ghcr.io' && github.token) }}
229+
230+
- name: Prepare references
231+
id: prepare
232+
env:
233+
DIGEST_X86_64: ${{ needs.build-image.outputs.digest_x86_64 }}
234+
DIGEST_AARCH64: ${{ needs.build-image.outputs.digest_aarch64 }}
235+
run: |
236+
# Parse environment variables and create references in one go
237+
references_data=$(printenv | jq -Rsc '[split("\n")[] | capture("^DIGEST_(?<arch>[^=]+)=(?<digest>.+)$") | .arch |= ascii_downcase]')
238+
239+
# Verify we have at least one digest
240+
if [[ "$(echo "$references_data" | jq -r 'length')" -eq 0 ]]; then
241+
echo "::error::No digest values found! Cannot create manifest list without at least one input reference."
242+
exit 1
243+
fi
244+
245+
# Log found architectures and their digests
246+
echo "$references_data" | jq -r '.[] | "Found digest for \(.arch): \(.digest)"'
247+
248+
# Create space-separated references string for the composite action
249+
references_string=$(echo "$references_data" | jq -r --arg ref "$IMAGE_REF" 'map($ref + "@" + .digest) | join(" ")')
250+
echo "references=${references_string}" >> "$GITHUB_OUTPUT"
251+
252+
- name: Merge OCI manifest lists
253+
id: merge
254+
uses: ./merge-oci-manifest-lists
255+
with:
256+
target: ${{ inputs.registry }}/${{ inputs.name }}:${{ inputs.tag }}
257+
references: ${{ steps.prepare.outputs.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)