Skip to content

Commit be9fa04

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

File tree

2 files changed

+320
-70
lines changed

2 files changed

+320
-70
lines changed
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
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+
--ruleset ruleset.yaml \
167+
openapi-head.yaml > vacuum-report.json
168+
169+
jq '.resultSet.results // []' vacuum-report.json > vacuum-results.json
170+
171+
ERROR_COUNT=$(jq '[.[] | select(.ruleSeverity == "error")] | length' vacuum-results.json)
172+
WARNING_COUNT=$(jq '[.[] | select(.ruleSeverity == "warn")] | length' vacuum-results.json)
173+
174+
echo "Found $ERROR_COUNT error(s) and $WARNING_COUNT warning(s)"
175+
176+
if [ "$ERROR_COUNT" -gt 0 ]; then
177+
echo "API specification audit failed with $ERROR_COUNT error(s)"
178+
echo ""
179+
echo "Errors:"
180+
jq -r '.[] | select(.ruleSeverity == "error") | " - [\(.ruleId)] \(.message) at \(.path)"' vacuum-results.json
181+
exit 1
182+
else
183+
echo "API specification audit passed!"
184+
fi
185+
186+
check-breaking-changes:
187+
name: Check breaking changes
188+
needs: [check-specs-identical]
189+
if: needs.check-specs-identical.outputs.specs-identical != 'true'
190+
runs-on: ubuntu-latest
191+
steps:
192+
- name: Download head API spec artifact
193+
uses: actions/download-artifact@v7
194+
with:
195+
name: openapi-head.yaml
196+
197+
- name: Download merge base API spec artifact
198+
uses: actions/download-artifact@v7
199+
with:
200+
name: openapi-merge-base.yaml
201+
202+
- name: Create static-analysis config file
203+
run: |
204+
cat <<EOF > .static-analysis.yaml
205+
---
206+
api_version: static-analysis.bitgo/v1alpha1
207+
208+
rules:
209+
- path: rules/openapi/breaking-changes
210+
severity: error
211+
options:
212+
before_spec_path: openapi-merge-base.yaml
213+
after_spec_path: openapi-head.yaml
214+
EOF
215+
- name: Generate GitHub App Token
216+
id: generate-github-app-token
217+
uses: actions/create-github-app-token@v2
218+
with:
219+
app-id: ${{ env.STATIC_ANALYSIS_BOT_APP_ID }}
220+
private-key: ${{ secrets.STATIC_ANALYSIS_BOT_PRIVATE_KEY }}
221+
owner: bitgo
222+
repositories: |
223+
static-analysis
224+
225+
- name: Install BitGo/static-analysis/static-analysis@v1
226+
uses: BitGo/install-github-release-binary@v2
227+
with:
228+
targets: BitGo/static-analysis/static-analysis@v1
229+
token: ${{ steps.generate-github-app-token.outputs.token }}
230+
231+
- name: Check breaking changes
232+
run: |
233+
if ! static-analysis; then
234+
echo "
235+
## ⚠️ Breaking Changes Detected
236+
237+
The OpenAPI spec changes in this PR contain breaking changes that could affect API consumers.
238+
239+
**What to do next:**
240+
1. Review the breaking changes in the workflow run
241+
2. If these breaking changes are intentional and necessary, contact the DevEx team for a manual override
242+
3. If not intentional, please revise your changes to maintain backward compatibility
243+
244+
**Need a manual override?**
245+
Contact the DevEx team to request a manual override.
246+
"
247+
exit 1
248+
fi
249+
250+
manual-linter-override:
251+
name: Linter Override
252+
needs: [check-breaking-changes]
253+
if: needs.check-breaking-changes.result == 'failure'
254+
environment: breaking-changes-override
255+
runs-on: ubuntu-latest
256+
steps:
257+
- name: Override Breaking Changes Check
258+
run: |
259+
echo "⚠️ Manual override requested for breaking changes check"
260+
echo "Breaking changes check failed but was manually approved to proceed"
261+
echo "This override was approved by the reviewer"
262+
263+
api-spec-check:
264+
name: API Spec Check
265+
needs:
266+
[
267+
generate-head-api-spec,
268+
generate-merge-base-api-spec,
269+
check-specs-identical,
270+
generate-vacuum-reports,
271+
check-breaking-changes,
272+
manual-linter-override,
273+
]
274+
runs-on: ubuntu-latest
275+
if: always()
276+
steps:
277+
- name: Check generate-head-api-spec
278+
env:
279+
GENERATE_HEAD_API_SPEC_RESULT: ${{ needs.generate-head-api-spec.result }}
280+
run: |
281+
if [ "$GENERATE_HEAD_API_SPEC_RESULT" != "success" ]; then
282+
echo "❌ generate-head-api-spec: ${{ needs.generate-head-api-spec.result }}"
283+
exit 1
284+
fi
285+
- name: Check generate-merge-base-api-spec
286+
env:
287+
GENERATE_MERGE_BASE_API_SPEC_RESULT: ${{ needs.generate-merge-base-api-spec.result }}
288+
run: |
289+
if [ "$GENERATE_MERGE_BASE_API_SPEC_RESULT" != "success" ]; then
290+
echo "❌ generate-merge-base-api-spec: ${{ needs.generate-merge-base-api-spec.result }}"
291+
exit 1
292+
fi
293+
- name: Check generate-vacuum-reports
294+
env:
295+
GENERATE_VACUUM_REPORTS_RESULT: ${{ needs.generate-vacuum-reports.result }}
296+
SPECS_IDENTICAL: ${{ needs.check-specs-identical.outputs.specs-identical }}
297+
run: |
298+
if [ "$GENERATE_VACUUM_REPORTS_RESULT" = "skipped" ] && [ "$SPECS_IDENTICAL" = "true" ]; then
299+
echo "⏭️ generate-vacuum-reports: skipped (specs are identical)"
300+
elif [ "$GENERATE_VACUUM_REPORTS_RESULT" != "success" ]; then
301+
echo "❌ generate-vacuum-reports: ${{ needs.generate-vacuum-reports.result }}"
302+
exit 1
303+
fi
304+
- name: Check check-breaking-changes
305+
env:
306+
CHECK_BREAKING_CHANGES_RESULT: ${{ needs.check-breaking-changes.result }}
307+
MANUAL_LINTER_OVERRIDE_RESULT: ${{ needs.manual-linter-override.result }}
308+
SPECS_IDENTICAL: ${{ needs.check-specs-identical.outputs.specs-identical }}
309+
run: |
310+
if [ "$CHECK_BREAKING_CHANGES_RESULT" = "skipped" ] && [ "$SPECS_IDENTICAL" = "true" ]; then
311+
echo "⏭️ check-breaking-changes: skipped (specs are identical)"
312+
elif [ "$CHECK_BREAKING_CHANGES_RESULT" != "success" ] && [ "$MANUAL_LINTER_OVERRIDE_RESULT" = "success" ]; then
313+
echo "⚠️ Manual linter override requested for breaking changes check"
314+
echo "Breaking changes check failed but was manually approved to proceed"
315+
elif [ "$CHECK_BREAKING_CHANGES_RESULT" != "success" ]; then
316+
echo "❌ check-breaking-changes: ${{ needs.check-breaking-changes.result }}"
317+
exit 1
318+
fi
319+
- name: Verify API Spec Check Passed
320+
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)