Skip to content

Commit ff75dfe

Browse files
ludamadclaude
andcommitted
refactor: separate CI workflow concerns into reusable ci3-call.yml
Created ci3-call.yml as a reusable workflow that both ci3.yml and ci3-external.yml can call securely with validated parameters. Security improvements: - All inputs are strongly typed to prevent injection attacks - Secrets passed via secrets: parameter, not inputs: to prevent log exposure - External PRs only receive minimal required secrets (AWS, SSH, GitHub token) - Internal secrets (GCP, Netlify, NPM, Docker, Slack) excluded from external runs - Security checks remain in caller workflows before calling reusable workflow - Checkout uses validated refs with persist-credentials: false Architecture: - ci3.yml: Internal PR workflow with prepare job that computes parameters - ci3-external.yml: External PR workflow with security checks in prepare job - ci3-call.yml: Shared execution logic called by both with typed parameters Benefits: - DRY principle: shared logic maintained in one place - Clear separation of concerns: security checks vs execution - Type safety: all parameters validated - Audit trail: explicit parameter passing Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 8183042 commit ff75dfe

File tree

3 files changed

+466
-161
lines changed

3 files changed

+466
-161
lines changed

.github/workflows/ci3-call.yml

Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
# Reusable CI workflow for Aztec
2+
# This workflow is called by both ci3.yml (internal) and ci3-external.yml (external contributors)
3+
#
4+
# SECURITY NOTES:
5+
# - All inputs are strongly typed to prevent injection
6+
# - Secrets are passed via secrets: not inputs: to prevent exposure in logs
7+
# - Caller workflows handle checkout and security checks
8+
# - This workflow only executes the CI logic with validated parameters
9+
name: CI3 Reusable Workflow
10+
11+
on:
12+
workflow_call:
13+
inputs:
14+
# CI execution mode
15+
ci_mode:
16+
description: 'CI mode to run (fast, full, merge-queue, docs, barretenberg, nightly, release)'
17+
required: true
18+
type: string
19+
20+
# Caching parameters
21+
cache_key:
22+
description: 'Full cache key for CI results'
23+
required: true
24+
type: string
25+
tree_hash:
26+
description: 'Git tree hash'
27+
required: true
28+
type: string
29+
30+
# Execution context
31+
is_external:
32+
description: 'Whether this is an external contributor run (affects available secrets)'
33+
required: true
34+
type: boolean
35+
run_id:
36+
description: 'GitHub run ID'
37+
required: true
38+
type: string
39+
repository:
40+
description: 'GitHub repository'
41+
required: true
42+
type: string
43+
checkout_ref:
44+
description: 'Git ref to checkout (PR head SHA or commit SHA)'
45+
required: true
46+
type: string
47+
checkout_fetch_depth:
48+
description: 'Fetch depth for checkout'
49+
required: false
50+
type: string
51+
default: '0'
52+
53+
# Optional: external-specific parameters
54+
ref_name:
55+
description: 'Reference name for external runs'
56+
required: false
57+
type: string
58+
default: ''
59+
arch:
60+
description: 'Architecture (external runs use amd64 only)'
61+
required: false
62+
type: string
63+
default: ''
64+
65+
# Optional: PR squash-merge parameters
66+
should_squash_merge:
67+
description: 'Whether to squash and merge the PR'
68+
required: false
69+
type: boolean
70+
default: false
71+
pr_number:
72+
description: 'PR number for squash-merge'
73+
required: false
74+
type: string
75+
default: ''
76+
pr_head_ref:
77+
description: 'PR head ref for squash-merge'
78+
required: false
79+
type: string
80+
default: ''
81+
pr_base_ref:
82+
description: 'PR base ref for squash-merge'
83+
required: false
84+
type: string
85+
default: ''
86+
pr_base_sha:
87+
description: 'PR base SHA for squash-merge'
88+
required: false
89+
type: string
90+
default: ''
91+
92+
# Optional: benchmark parameters
93+
should_upload_benchmarks:
94+
description: 'Whether to download and upload benchmarks'
95+
required: false
96+
type: boolean
97+
default: false
98+
target_branch:
99+
description: 'Target branch for benchmarks'
100+
required: false
101+
type: string
102+
default: ''
103+
pr_head_sha:
104+
description: 'PR head SHA for benchmark ref'
105+
required: false
106+
type: string
107+
default: ''
108+
github_sha:
109+
description: 'GitHub SHA for benchmark ref'
110+
required: false
111+
type: string
112+
default: ''
113+
114+
secrets:
115+
# Required for all runs
116+
AWS_ACCESS_KEY_ID:
117+
required: true
118+
AWS_SECRET_ACCESS_KEY:
119+
required: true
120+
BUILD_INSTANCE_SSH_KEY:
121+
required: true
122+
AZTEC_BOT_GITHUB_TOKEN:
123+
required: true
124+
125+
# Optional: only for internal runs (not passed to external)
126+
GCP_SA_KEY:
127+
required: false
128+
NETLIFY_SITE_ID:
129+
required: false
130+
NETLIFY_AUTH_TOKEN:
131+
required: false
132+
DOCKERHUB_PASSWORD:
133+
required: false
134+
NPM_TOKEN:
135+
required: false
136+
SLACK_BOT_TOKEN:
137+
required: false
138+
GCP_SEPOLIA_URL:
139+
required: false
140+
GCP_SEPOLIA_API_KEY:
141+
required: false
142+
INFURA_SEPOLIA_URL:
143+
required: false
144+
GCP_PROJECT_ID:
145+
required: false
146+
147+
jobs:
148+
ci-run:
149+
runs-on: ubuntu-latest
150+
env:
151+
GOOGLE_APPLICATION_CREDENTIALS: /tmp/gcp-key.json
152+
steps:
153+
#############
154+
# Checkout
155+
#############
156+
- name: Checkout
157+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
158+
with:
159+
ref: ${{ inputs.checkout_ref }}
160+
fetch-depth: ${{ inputs.checkout_fetch_depth }}
161+
persist-credentials: false
162+
163+
#############
164+
# Setup
165+
#############
166+
- name: Setup SSH Key
167+
run: |
168+
# Ensure we can SSH into the spot instances we request.
169+
mkdir -p ~/.ssh
170+
echo ${{ secrets.BUILD_INSTANCE_SSH_KEY }} | base64 --decode > ~/.ssh/build_instance_key
171+
chmod 600 ~/.ssh/build_instance_key
172+
173+
- name: Store GCP Key (Internal Only)
174+
if: ${{ !inputs.is_external && secrets.GCP_SA_KEY != '' }}
175+
env:
176+
GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
177+
run: |
178+
set +x
179+
umask 077
180+
printf '%s' "$GCP_SA_KEY" > "$GOOGLE_APPLICATION_CREDENTIALS"
181+
jq -e . "$GOOGLE_APPLICATION_CREDENTIALS" >/dev/null
182+
183+
#############
184+
# Check Cache
185+
#############
186+
- name: Check CI Cache
187+
id: ci_cache
188+
uses: actions/cache@v3
189+
with:
190+
path: ci-success.txt
191+
key: ${{ inputs.cache_key }}
192+
193+
- name: CI Cached
194+
if: steps.ci_cache.outputs.cache-hit == 'true'
195+
run: |
196+
echo "Previous run: $(cat ci-success.txt)"
197+
198+
#############
199+
# Run CI
200+
#############
201+
- name: Run CI
202+
if: steps.ci_cache.outputs.cache-hit != 'true'
203+
env:
204+
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
205+
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
206+
GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
207+
RUN_ID: ${{ inputs.run_id }}
208+
# External-specific env vars
209+
REF_NAME: ${{ inputs.ref_name }}
210+
ARCH: ${{ inputs.arch }}
211+
# Internal-only env vars (only set if not external)
212+
NETLIFY_SITE_ID: ${{ !inputs.is_external && secrets.NETLIFY_SITE_ID || '' }}
213+
NETLIFY_AUTH_TOKEN: ${{ !inputs.is_external && secrets.NETLIFY_AUTH_TOKEN || '' }}
214+
DOCKERHUB_PASSWORD: ${{ !inputs.is_external && secrets.DOCKERHUB_PASSWORD || '' }}
215+
NPM_TOKEN: ${{ !inputs.is_external && secrets.NPM_TOKEN || '' }}
216+
SLACK_BOT_TOKEN: ${{ !inputs.is_external && secrets.SLACK_BOT_TOKEN || '' }}
217+
GOOGLE_APPLICATION_CREDENTIALS: ${{ !inputs.is_external && env.GOOGLE_APPLICATION_CREDENTIALS || '' }}
218+
EXTERNAL_ETHEREUM_HOSTS: ${{ !inputs.is_external && format('https://json-rpc.{0}?key={1},{2}', secrets.GCP_SEPOLIA_URL, secrets.GCP_SEPOLIA_API_KEY, secrets.INFURA_SEPOLIA_URL) || '' }}
219+
EXTERNAL_ETHEREUM_CONSENSUS_HOST: ${{ !inputs.is_external && format('https://beacon.{0}', secrets.GCP_SEPOLIA_URL) || '' }}
220+
EXTERNAL_ETHEREUM_CONSENSUS_HOST_API_KEY: ${{ !inputs.is_external && secrets.GCP_SEPOLIA_API_KEY || '' }}
221+
EXTERNAL_ETHEREUM_CONSENSUS_HOST_API_KEY_HEADER: ${{ !inputs.is_external && 'X-goog-api-key' || '' }}
222+
GCP_PROJECT_ID: ${{ !inputs.is_external && secrets.GCP_PROJECT_ID || '' }}
223+
run: |
224+
# Execute CI based on the specified mode
225+
case "${{ inputs.ci_mode }}" in
226+
merge-queue)
227+
exec ./ci.sh merge-queue
228+
;;
229+
full)
230+
exec ./ci.sh full
231+
;;
232+
docs)
233+
exec ./ci.sh docs
234+
;;
235+
barretenberg)
236+
exec ./ci.sh barretenberg
237+
;;
238+
nightly)
239+
exec ./ci.sh nightly
240+
;;
241+
release)
242+
exec ./ci.sh release
243+
;;
244+
fast)
245+
exec ./ci.sh fast
246+
;;
247+
*)
248+
echo "Error: Unknown CI mode: ${{ inputs.ci_mode }}"
249+
exit 1
250+
;;
251+
esac
252+
253+
- name: Save CI Success
254+
if: steps.ci_cache.outputs.cache-hit != 'true'
255+
run: echo "https://github.com/${{ inputs.repository }}/actions/runs/${{ inputs.run_id }}" > ci-success.txt
256+
257+
#############
258+
# Squash and Merge (if requested)
259+
#############
260+
- name: CI Squash and Merge
261+
if: inputs.should_squash_merge
262+
env:
263+
GITHUB_TOKEN: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
264+
run: |
265+
# Reauth the git repo with our GITHUB_TOKEN
266+
git remote set-url origin https://x-access-token:${GITHUB_TOKEN}@github.com/${{ inputs.repository }}
267+
# Get the base commit (merge-base) for the PR
268+
./scripts/merge-train/squash-pr.sh \
269+
"${{ inputs.pr_number }}" \
270+
"${{ inputs.pr_head_ref }}" \
271+
"${{ inputs.pr_base_ref }}" \
272+
"${{ inputs.pr_base_sha }}"
273+
gh pr edit "${{ inputs.pr_number }}" --remove-label "ci-squash-and-merge"
274+
gh pr merge "${{ inputs.pr_number }}" --auto -m || true
275+
276+
#############
277+
# Benchmarks (internal only)
278+
#############
279+
- name: Download benchmarks
280+
if: inputs.should_upload_benchmarks && !inputs.is_external
281+
run: ./ci.sh gh-bench
282+
283+
- name: Upload benchmarks
284+
if: inputs.should_upload_benchmarks && !inputs.is_external
285+
uses: benchmark-action/github-action-benchmark@4de1bed97a47495fc4c5404952da0499e31f5c29
286+
with:
287+
name: Aztec Benchmarks
288+
benchmark-data-dir-path: "bench/${{ inputs.target_branch }}"
289+
tool: "customSmallerIsBetter"
290+
output-file-path: ./bench-out/bench.json
291+
github-token: ${{ secrets.AZTEC_BOT_GITHUB_TOKEN }}
292+
auto-push: true
293+
ref: ${{ inputs.pr_head_sha || inputs.github_sha }}
294+
alert-threshold: "105%"
295+
comment-on-alert: false
296+
fail-on-alert: false
297+
max-items-in-chart: 100

0 commit comments

Comments
 (0)