Skip to content

Commit b79ba3d

Browse files
Merge pull request #2824 from Sefaria/multienv-deploy
feat: abstract image build steps into reusable workflow
2 parents 820c7bc + 8c078ff commit b79ba3d

File tree

3 files changed

+316
-300
lines changed

3 files changed

+316
-300
lines changed
Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
name: Build and Push Container Images
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
git_ref:
7+
description: 'Git reference to build from'
8+
required: true
9+
type: string
10+
image_tag:
11+
description: 'Tag to apply to the images'
12+
required: true
13+
type: string
14+
branch_name:
15+
description: 'Formatted branch name for image naming'
16+
required: true
17+
type: string
18+
environment:
19+
description: 'Target environment (pr, staging, preprod, production)'
20+
required: true
21+
type: string
22+
push_to_prod_registry:
23+
description: 'Whether to push to production registry (true for staging/preprod/prod)'
24+
required: false
25+
type: boolean
26+
default: false
27+
outputs:
28+
web_digest:
29+
description: 'Digest of the web image'
30+
value: ${{ jobs.build-web.outputs.digest }}
31+
node_digest:
32+
description: 'Digest of the node image'
33+
value: ${{ jobs.build-node.outputs.digest }}
34+
asset_digest:
35+
description: 'Digest of the asset image'
36+
value: ${{ jobs.build-asset.outputs.digest }}
37+
image_tag:
38+
description: 'The tag applied to all images'
39+
value: ${{ inputs.image_tag }}
40+
web_image:
41+
description: 'Full web image reference'
42+
value: ${{ jobs.build-web.outputs.image }}
43+
node_image:
44+
description: 'Full node image reference'
45+
value: ${{ jobs.build-node.outputs.image }}
46+
asset_image:
47+
description: 'Full asset image reference'
48+
value: ${{ jobs.build-asset.outputs.image }}
49+
50+
jobs:
51+
build-generic:
52+
name: "Build ${{ matrix.app }} Image"
53+
runs-on: ubuntu-latest
54+
permissions:
55+
contents: 'read'
56+
id-token: 'write'
57+
strategy:
58+
matrix:
59+
app: [ web, node ]
60+
outputs:
61+
web_digest: ${{ steps.output-web.outputs.digest }}
62+
web_image: ${{ steps.output-web.outputs.image }}
63+
node_digest: ${{ steps.output-node.outputs.digest }}
64+
node_image: ${{ steps.output-node.outputs.image }}
65+
steps:
66+
- name: Checkout code
67+
uses: actions/checkout@v4
68+
with:
69+
ref: ${{ inputs.git_ref }}
70+
- name: Set up QEMU
71+
uses: docker/setup-qemu-action@v3
72+
- name: Set up Docker Buildx
73+
uses: docker/setup-buildx-action@v3
74+
- id: auth
75+
name: Authenticate to Google Cloud
76+
uses: google-github-actions/auth@v2
77+
with:
78+
token_format: 'access_token'
79+
workload_identity_provider: ${{ inputs.push_to_prod_registry && format('projects/{0}/locations/global/workloadIdentityPools/github/providers/github', secrets.PROD_GKE_PROJECT_ID) || format('projects/{0}/locations/global/workloadIdentityPools/github/providers/github', secrets.DEV_GKE_PROJECT_ID) }}
80+
service_account: ${{ inputs.push_to_prod_registry && secrets.PROD_GKE_SA || secrets.DEV_GKE_SA }}
81+
- name: Login to GAR
82+
uses: docker/login-action@v3
83+
with:
84+
registry: us-east1-docker.pkg.dev
85+
username: oauth2accesstoken
86+
password: '${{ steps.auth.outputs.access_token }}'
87+
- name: Get current date
88+
id: date
89+
run: echo "date=$(date +'%Y%m%d%H%M')" >> $GITHUB_OUTPUT
90+
- name: Set registry path
91+
id: registry
92+
run: |
93+
if [ "${{ inputs.push_to_prod_registry }}" = "true" ]; then
94+
echo "project=${{ secrets.PROD_PROJECT }}" >> $GITHUB_OUTPUT
95+
else
96+
echo "project=${{ secrets.DEV_PROJECT }}" >> $GITHUB_OUTPUT
97+
fi
98+
- name: Determine Image URI
99+
id: image_uri
100+
run: |
101+
if [ "${{ inputs.branch_name }}" = "" ]; then
102+
echo "image_uri=us-east1-docker.pkg.dev/${{ steps.registry.outputs.project }}/containers/sefaria-${{ matrix.app }}" >> $GITHUB_OUTPUT
103+
else
104+
echo "image_uri=us-east1-docker.pkg.dev/${{ steps.registry.outputs.project }}/containers/sefaria-${{ matrix.app }}-${{ inputs.branch_name }}" >> $GITHUB_OUTPUT
105+
fi
106+
- name: Generate image metadata
107+
id: meta
108+
uses: docker/metadata-action@v3
109+
with:
110+
images: |
111+
${{ steps.image_uri.outputs.image_uri }}
112+
tags: |
113+
type=raw,value=${{ inputs.image_tag }}
114+
type=raw,value=${{ inputs.image_tag }}-${{ steps.date.outputs.date }}
115+
type=raw,value=latest
116+
flavor: |
117+
latest=false
118+
- name: Build and push
119+
id: build
120+
uses: docker/build-push-action@v6
121+
with:
122+
context: .
123+
push: true
124+
build-args: |
125+
TYPE=build
126+
file: ./build/${{ matrix.app }}/Dockerfile
127+
tags: ${{ steps.meta.outputs.tags }}
128+
labels: ${{ steps.meta.outputs.labels }}
129+
# Output digest for web
130+
- name: Output web digest
131+
id: output-web
132+
if: matrix.app == 'web'
133+
run: |
134+
echo "digest=${{ steps.build.outputs.digest }}" >> $GITHUB_OUTPUT
135+
echo "image=${{steps.image_uri.outputs.image_uri }}" >> $GITHUB_OUTPUT
136+
# Output digest for node
137+
- name: Output node digest
138+
id: output-node
139+
if: matrix.app == 'node'
140+
run: |
141+
echo "digest=${{ steps.build.outputs.digest }}" >> $GITHUB_OUTPUT
142+
echo "image=${{steps.image_uri.outputs.image_uri }}" >> $GITHUB_OUTPUT
143+
144+
# Consolidate outputs from matrix job
145+
build-web:
146+
name: "Web Image Output"
147+
runs-on: ubuntu-latest
148+
needs: build-generic
149+
outputs:
150+
digest: ${{ needs.build-generic.outputs.web_digest }}
151+
image: ${{ needs.build-generic.outputs.web_image }}
152+
steps:
153+
- run: echo "Web image built"
154+
155+
build-node:
156+
name: "Node Image Output"
157+
runs-on: ubuntu-latest
158+
needs: build-generic
159+
outputs:
160+
digest: ${{ needs.build-generic.outputs.node_digest }}
161+
image: ${{ needs.build-generic.outputs.node_image }}
162+
steps:
163+
- run: echo "Node image built"
164+
165+
# Build derived image (asset) that depends on web
166+
build-asset:
167+
name: "Build Asset Image"
168+
runs-on: ubuntu-latest
169+
needs: build-generic
170+
permissions:
171+
contents: 'read'
172+
id-token: 'write'
173+
outputs:
174+
digest: ${{ steps.build.outputs.digest }}
175+
image: ${{ steps.output.outputs.image }}
176+
steps:
177+
- name: Checkout code
178+
uses: actions/checkout@v4
179+
with:
180+
ref: ${{ inputs.git_ref }}
181+
- name: Set up QEMU
182+
uses: docker/setup-qemu-action@v3
183+
- name: Set up Docker Buildx
184+
uses: docker/setup-buildx-action@v3
185+
- id: auth
186+
name: Authenticate to Google Cloud
187+
uses: google-github-actions/auth@v2
188+
with:
189+
token_format: 'access_token'
190+
workload_identity_provider: ${{ inputs.push_to_prod_registry && format('projects/{0}/locations/global/workloadIdentityPools/github/providers/github', secrets.PROD_GKE_PROJECT_ID) || format('projects/{0}/locations/global/workloadIdentityPools/github/providers/github', secrets.DEV_GKE_PROJECT_ID) }}
191+
service_account: ${{ inputs.push_to_prod_registry && secrets.PROD_GKE_SA || secrets.DEV_GKE_SA }}
192+
- name: Login to GAR
193+
uses: docker/login-action@v3
194+
with:
195+
registry: us-east1-docker.pkg.dev
196+
username: oauth2accesstoken
197+
password: '${{ steps.auth.outputs.access_token }}'
198+
- name: Get current date
199+
id: date
200+
run: echo "date=$(date +'%Y%m%d%H%M')" >> $GITHUB_OUTPUT
201+
- name: Set registry path
202+
id: registry
203+
run: |
204+
if [ "${{ inputs.push_to_prod_registry }}" = "true" ]; then
205+
echo "project=${{ secrets.PROD_PROJECT }}" >> $GITHUB_OUTPUT
206+
else
207+
echo "project=${{ secrets.DEV_PROJECT }}" >> $GITHUB_OUTPUT
208+
fi
209+
- name: Generate image metadata
210+
id: meta
211+
uses: docker/metadata-action@v3
212+
with:
213+
images: |
214+
us-east1-docker.pkg.dev/${{ steps.registry.outputs.project }}/containers/sefaria-asset-${{ inputs.branch_name }}
215+
tags: |
216+
type=raw,value=${{ inputs.image_tag }}
217+
type=raw,value=${{ inputs.image_tag }}-${{ steps.date.outputs.date }}
218+
type=raw,value=latest
219+
flavor: |
220+
latest=false
221+
- name: Build and push
222+
id: build
223+
uses: docker/build-push-action@v6
224+
with:
225+
context: .
226+
push: true
227+
build-args: |
228+
SRC_IMG=us-east1-docker.pkg.dev/${{ steps.registry.outputs.project }}/containers/sefaria-web-${{ inputs.branch_name }}:${{ inputs.image_tag }}
229+
file: ./build/asset/Dockerfile
230+
tags: ${{ steps.meta.outputs.tags }}
231+
labels: ${{ steps.meta.outputs.labels }}
232+
- name: Output asset info
233+
id: output
234+
run: |
235+
echo "image=us-east1-docker.pkg.dev/${{ steps.registry.outputs.project }}/containers/sefaria-asset-${{ inputs.branch_name }}:${{ inputs.image_tag }}" >> $GITHUB_OUTPUT
236+
237+
# Summary job
238+
summary:
239+
name: Build Summary
240+
runs-on: ubuntu-latest
241+
needs: [build-web, build-node, build-asset]
242+
if: always()
243+
steps:
244+
- name: Output build summary
245+
run: |
246+
echo "### Docker Images Built and Pushed :whale:" >> $GITHUB_STEP_SUMMARY
247+
echo "" >> $GITHUB_STEP_SUMMARY
248+
echo "**Environment:** ${{ inputs.environment }}" >> $GITHUB_STEP_SUMMARY
249+
echo "**Branch Name:** ${{ inputs.branch_name }}" >> $GITHUB_STEP_SUMMARY
250+
echo "**Tag:** ${{ inputs.image_tag }}" >> $GITHUB_STEP_SUMMARY
251+
echo "" >> $GITHUB_STEP_SUMMARY
252+
echo "| Image | Status | Digest |" >> $GITHUB_STEP_SUMMARY
253+
echo "|-------|--------|--------|" >> $GITHUB_STEP_SUMMARY
254+
echo "| sefaria-web | ${{ needs.build-web.result }} | \`${{ needs.build-web.outputs.digest }}\` |" >> $GITHUB_STEP_SUMMARY
255+
echo "| sefaria-node | ${{ needs.build-node.result }} | \`${{ needs.build-node.outputs.digest }}\` |" >> $GITHUB_STEP_SUMMARY
256+
echo "| sefaria-asset | ${{ needs.build-asset.result }} | \`${{ needs.build-asset.outputs.digest }}\` |" >> $GITHUB_STEP_SUMMARY

0 commit comments

Comments
 (0)