Skip to content

Commit 831a387

Browse files
committed
ci: move audit-api-spec to separate workflow
Ticket: DX-2592
1 parent ab2af84 commit 831a387

File tree

2 files changed

+319
-70
lines changed

2 files changed

+319
-70
lines changed
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
name: Audit API Spec
2+
3+
on:
4+
pull_request:
5+
6+
env:
7+
STATIC_ANALYSIS_BOT_APP_ID: 1819979
8+
jobs:
9+
generate-head-api-spec:
10+
name: Generate head API spec
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout PR
14+
uses: actions/checkout@v6
15+
with:
16+
ref: ${{ github.event.pull_request.head.sha }}
17+
18+
- name: Setup Node.js
19+
uses: actions/setup-node@v6
20+
with:
21+
node-version: 22
22+
23+
- name: Cache npm dependencies
24+
id: node-modules-cache
25+
uses: actions/cache@v5
26+
with:
27+
path: "**/node_modules"
28+
key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }}
29+
30+
- name: Install Dependencies
31+
if: steps.node-modules-cache.outputs.cache-hit != 'true'
32+
run: npm ci
33+
34+
- name: Generate API spec
35+
run: |
36+
./node_modules/.bin/openapi-generator \
37+
src/masterBitgoExpress/routers/index.ts \
38+
> generated.json
39+
40+
- name: Remove unknown tags from generated spec
41+
run: |
42+
jq '(.paths[] | .[]? | select(. != null)) |= del(."x-unknown-tags")' generated.json > openapi.json
43+
44+
- name: Convert merged spec to YAML
45+
run: yq -P < openapi.json > openapi-head.yaml
46+
47+
- name: Upload API spec to artifact
48+
uses: actions/upload-artifact@v6
49+
with:
50+
name: openapi-head.yaml
51+
path: openapi-head.yaml
52+
53+
generate-merge-base-api-spec:
54+
name: Generate merge base API spec
55+
runs-on: ubuntu-latest
56+
steps:
57+
- name: Checkout PR
58+
uses: actions/checkout@v6
59+
with:
60+
fetch-depth: 0
61+
62+
- name: Find and checkout merge base
63+
run: |
64+
git fetch origin ${{ github.event.pull_request.base.ref }}
65+
MERGE_BASE=$(git merge-base HEAD origin/${{ github.event.pull_request.base.ref }})
66+
echo "Merge base commit: $MERGE_BASE"
67+
git checkout $MERGE_BASE
68+
69+
- name: Setup Node.js
70+
uses: actions/setup-node@v6
71+
with:
72+
node-version: 22
73+
74+
- name: Cache npm dependencies
75+
id: node-modules-cache
76+
uses: actions/cache@v5
77+
with:
78+
path: "**/node_modules"
79+
key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }}
80+
81+
- name: Install Dependencies
82+
if: steps.node-modules-cache.outputs.cache-hit != 'true'
83+
run: npm ci
84+
85+
- name: Generate API spec
86+
run: |
87+
./node_modules/.bin/openapi-generator \
88+
src/masterBitgoExpress/routers/index.ts \
89+
> generated.json
90+
91+
- name: Remove unknown tags from generated spec
92+
run: |
93+
jq '(.paths[] | .[]? | select(. != null)) |= del(."x-unknown-tags")' generated.json > openapi.json
94+
95+
- name: Convert merged spec to YAML
96+
run: yq -P < openapi.json > openapi-merge-base.yaml
97+
98+
- name: Upload API spec to artifact
99+
uses: actions/upload-artifact@v6
100+
with:
101+
name: openapi-merge-base.yaml
102+
path: openapi-merge-base.yaml
103+
104+
check-specs-identical:
105+
name: Check specs identical
106+
runs-on: ubuntu-latest
107+
needs: [generate-head-api-spec, generate-merge-base-api-spec]
108+
outputs:
109+
specs-identical: ${{ steps.check-specs-identical.outputs.identical }}
110+
steps:
111+
- name: Download head API spec artifact
112+
uses: actions/download-artifact@v7
113+
with:
114+
name: openapi-head.yaml
115+
116+
- name: Download merge base API spec artifact
117+
uses: actions/download-artifact@v7
118+
with:
119+
name: openapi-merge-base.yaml
120+
121+
- name: Check specs identical
122+
id: check-specs-identical
123+
run: |
124+
if diff -q openapi-head.yaml openapi-merge-base.yaml > /dev/null 2>&1; then
125+
echo "identical=true" >> $GITHUB_OUTPUT
126+
echo "✅ Specs are identical - no changes detected, skipping subsequent checks"
127+
else
128+
echo "identical=false" >> $GITHUB_OUTPUT
129+
echo "📝 Specs differ - proceeding with audit checks"
130+
fi
131+
132+
generate-vacuum-reports:
133+
name: Generate vacuum reports for API spec
134+
runs-on: ubuntu-latest
135+
needs: [check-specs-identical]
136+
if: needs.check-specs-identical.outputs.specs-identical != 'true'
137+
steps:
138+
- name: Checkout PR head for ruleset
139+
uses: actions/checkout@v6
140+
with:
141+
ref: ${{ github.event.pull_request.head.sha }}
142+
143+
- name: Download head API spec artifact
144+
uses: actions/download-artifact@v7
145+
with:
146+
name: openapi-head.yaml
147+
148+
- name: Download and install vacuum v0.18.1
149+
run: |
150+
curl -L \
151+
--output vacuum.tar.gz \
152+
--silent \
153+
--show-error \
154+
--fail \
155+
https://github.com/daveshanley/vacuum/releases/download/v0.18.1/vacuum_0.18.1_linux_x86_64.tar.gz
156+
tar -xzf vacuum.tar.gz
157+
chmod u+x vacuum
158+
sudo mv vacuum /usr/local/bin/
159+
vacuum version
160+
161+
- name: Audit head API spec with Vacuum
162+
run: |
163+
vacuum report \
164+
--no-style \
165+
--stdout \
166+
openapi-head.yaml > vacuum-report.json
167+
168+
jq '.resultSet.results // []' vacuum-report.json > vacuum-results.json
169+
170+
ERROR_COUNT=$(jq '[.[] | select(.ruleSeverity == "error")] | length' vacuum-results.json)
171+
WARNING_COUNT=$(jq '[.[] | select(.ruleSeverity == "warn")] | length' vacuum-results.json)
172+
173+
echo "Found $ERROR_COUNT error(s) and $WARNING_COUNT warning(s)"
174+
175+
if [ "$ERROR_COUNT" -gt 0 ]; then
176+
echo "API specification audit failed with $ERROR_COUNT error(s)"
177+
echo ""
178+
echo "Errors:"
179+
jq -r '.[] | select(.ruleSeverity == "error") | " - [\(.ruleId)] \(.message) at \(.path)"' vacuum-results.json
180+
exit 1
181+
else
182+
echo "API specification audit passed!"
183+
fi
184+
185+
check-breaking-changes:
186+
name: Check breaking changes
187+
needs: [check-specs-identical]
188+
if: needs.check-specs-identical.outputs.specs-identical != 'true'
189+
runs-on: ubuntu-latest
190+
steps:
191+
- name: Download head API spec artifact
192+
uses: actions/download-artifact@v7
193+
with:
194+
name: openapi-head.yaml
195+
196+
- name: Download merge base API spec artifact
197+
uses: actions/download-artifact@v7
198+
with:
199+
name: openapi-merge-base.yaml
200+
201+
- name: Create static-analysis config file
202+
run: |
203+
cat <<EOF > .static-analysis.yaml
204+
---
205+
api_version: static-analysis.bitgo/v1alpha1
206+
207+
rules:
208+
- path: rules/openapi/breaking-changes
209+
severity: error
210+
options:
211+
before_spec_path: openapi-merge-base.yaml
212+
after_spec_path: openapi-head.yaml
213+
EOF
214+
- name: Generate GitHub App Token
215+
id: generate-github-app-token
216+
uses: actions/create-github-app-token@v2
217+
with:
218+
app-id: ${{ env.STATIC_ANALYSIS_BOT_APP_ID }}
219+
private-key: ${{ secrets.STATIC_ANALYSIS_BOT_PRIVATE_KEY }}
220+
owner: bitgo
221+
repositories: |
222+
static-analysis
223+
224+
- name: Install BitGo/static-analysis/static-analysis@v1
225+
uses: BitGo/install-github-release-binary@v2
226+
with:
227+
targets: BitGo/static-analysis/static-analysis@v1
228+
token: ${{ steps.generate-github-app-token.outputs.token }}
229+
230+
- name: Check breaking changes
231+
run: |
232+
if ! static-analysis; then
233+
echo "
234+
## ⚠️ Breaking Changes Detected
235+
236+
The OpenAPI spec changes in this PR contain breaking changes that could affect API consumers.
237+
238+
**What to do next:**
239+
1. Review the breaking changes in the workflow run
240+
2. If these breaking changes are intentional and necessary, contact the DevEx team for a manual override
241+
3. If not intentional, please revise your changes to maintain backward compatibility
242+
243+
**Need a manual override?**
244+
Contact the DevEx team to request a manual override.
245+
"
246+
exit 1
247+
fi
248+
249+
manual-linter-override:
250+
name: Linter Override
251+
needs: [check-breaking-changes]
252+
if: needs.check-breaking-changes.result == 'failure'
253+
environment: breaking-changes-override
254+
runs-on: ubuntu-latest
255+
steps:
256+
- name: Override Breaking Changes Check
257+
run: |
258+
echo "⚠️ Manual override requested for breaking changes check"
259+
echo "Breaking changes check failed but was manually approved to proceed"
260+
echo "This override was approved by the reviewer"
261+
262+
api-spec-check:
263+
name: API Spec Check
264+
needs:
265+
[
266+
generate-head-api-spec,
267+
generate-merge-base-api-spec,
268+
check-specs-identical,
269+
generate-vacuum-reports,
270+
check-breaking-changes,
271+
manual-linter-override,
272+
]
273+
runs-on: ubuntu-latest
274+
if: always()
275+
steps:
276+
- name: Check generate-head-api-spec
277+
env:
278+
GENERATE_HEAD_API_SPEC_RESULT: ${{ needs.generate-head-api-spec.result }}
279+
run: |
280+
if [ "$GENERATE_HEAD_API_SPEC_RESULT" != "success" ]; then
281+
echo "❌ generate-head-api-spec: ${{ needs.generate-head-api-spec.result }}"
282+
exit 1
283+
fi
284+
- name: Check generate-merge-base-api-spec
285+
env:
286+
GENERATE_MERGE_BASE_API_SPEC_RESULT: ${{ needs.generate-merge-base-api-spec.result }}
287+
run: |
288+
if [ "$GENERATE_MERGE_BASE_API_SPEC_RESULT" != "success" ]; then
289+
echo "❌ generate-merge-base-api-spec: ${{ needs.generate-merge-base-api-spec.result }}"
290+
exit 1
291+
fi
292+
- name: Check generate-vacuum-reports
293+
env:
294+
GENERATE_VACUUM_REPORTS_RESULT: ${{ needs.generate-vacuum-reports.result }}
295+
SPECS_IDENTICAL: ${{ needs.check-specs-identical.outputs.specs-identical }}
296+
run: |
297+
if [ "$GENERATE_VACUUM_REPORTS_RESULT" = "skipped" ] && [ "$SPECS_IDENTICAL" = "true" ]; then
298+
echo "⏭️ generate-vacuum-reports: skipped (specs are identical)"
299+
elif [ "$GENERATE_VACUUM_REPORTS_RESULT" != "success" ]; then
300+
echo "❌ generate-vacuum-reports: ${{ needs.generate-vacuum-reports.result }}"
301+
exit 1
302+
fi
303+
- name: Check check-breaking-changes
304+
env:
305+
CHECK_BREAKING_CHANGES_RESULT: ${{ needs.check-breaking-changes.result }}
306+
MANUAL_LINTER_OVERRIDE_RESULT: ${{ needs.manual-linter-override.result }}
307+
SPECS_IDENTICAL: ${{ needs.check-specs-identical.outputs.specs-identical }}
308+
run: |
309+
if [ "$CHECK_BREAKING_CHANGES_RESULT" = "skipped" ] && [ "$SPECS_IDENTICAL" = "true" ]; then
310+
echo "⏭️ check-breaking-changes: skipped (specs are identical)"
311+
elif [ "$CHECK_BREAKING_CHANGES_RESULT" != "success" ] && [ "$MANUAL_LINTER_OVERRIDE_RESULT" = "success" ]; then
312+
echo "⚠️ Manual linter override requested for breaking changes check"
313+
echo "Breaking changes check failed but was manually approved to proceed"
314+
elif [ "$CHECK_BREAKING_CHANGES_RESULT" != "success" ]; then
315+
echo "❌ check-breaking-changes: ${{ needs.check-breaking-changes.result }}"
316+
exit 1
317+
fi
318+
- name: Verify API Spec Check Passed
319+
run: echo "✅ All API specification checks passed successfully!"

.github/workflows/pull_request.yaml

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -41,73 +41,3 @@ jobs:
4141
VCS_REF=${{ github.sha }}
4242
cache-from: type=gha
4343
cache-to: type=gha,mode=max
44-
45-
audit-api-spec:
46-
name: Audit API Spec
47-
runs-on: ubuntu-latest
48-
49-
steps:
50-
- name: Checkout PR
51-
uses: actions/checkout@v4
52-
with:
53-
ref: ${{ github.event.pull_request.head.sha }}
54-
55-
- name: Setup Node.js
56-
uses: actions/setup-node@v4
57-
with:
58-
node-version: 22
59-
- name: Cache npm dependencies
60-
id: node-modules-cache
61-
uses: actions/cache@v4
62-
with:
63-
path: '**/node_modules'
64-
key: ${{ runner.os }}-modules-${{ hashFiles('**/package-lock.json') }}
65-
restore-keys: |
66-
${{ runner.os }}-modules-
67-
- name: Install Dependencies
68-
if: steps.node-modules-cache.outputs.cache-hit != 'true'
69-
run: npm ci
70-
71-
- name: Download and install vacuum v0.18.1
72-
run: |
73-
curl -L \
74-
--output vacuum.tar.gz \
75-
--silent \
76-
--show-error \
77-
--fail \
78-
https://github.com/daveshanley/vacuum/releases/download/v0.18.1/vacuum_0.18.1_linux_x86_64.tar.gz
79-
tar -xzf vacuum.tar.gz
80-
chmod u+x vacuum
81-
sudo mv vacuum /usr/local/bin/
82-
vacuum version
83-
84-
- name: Generate API spec
85-
run: |
86-
./node_modules/.bin/openapi-generator \
87-
src/masterBitgoExpress/routers/index.ts \
88-
> api-generated.json
89-
90-
- name: Audit with Vacuum
91-
run: |
92-
vacuum report \
93-
--no-style \
94-
--stdout \
95-
--ruleset ruleset.yaml \
96-
api-generated.json > vacuum-report.json
97-
98-
jq '.resultSet.results // []' vacuum-report.json > vacuum-results.json
99-
100-
ERROR_COUNT=$(jq '[.[] | select(.ruleSeverity == "error")] | length' vacuum-results.json)
101-
WARNING_COUNT=$(jq '[.[] | select(.ruleSeverity == "warn")] | length' vacuum-results.json)
102-
103-
echo "Found $ERROR_COUNT error(s) and $WARNING_COUNT warning(s)"
104-
105-
if [ "$ERROR_COUNT" -gt 0 ]; then
106-
echo "API specification audit failed with $ERROR_COUNT error(s)"
107-
echo ""
108-
echo "Errors:"
109-
jq -r '.[] | select(.ruleSeverity == "error") | " - [\(.ruleId)] \(.message) at \(.path)"' vacuum-results.json
110-
exit 1
111-
else
112-
echo "API specification audit passed!"
113-
fi

0 commit comments

Comments
 (0)