Skip to content

Commit 2941b14

Browse files
ci(ipa): automatic warning-level violation detection
1 parent b3e496a commit 2941b14

File tree

7 files changed

+1942
-9
lines changed

7 files changed

+1942
-9
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/bin/bash
2+
set -eou pipefail
3+
4+
WARNING_COUNT=$1
5+
TEAM_ID=$2
6+
JIRA_API_TOKEN=$3
7+
SLACK_BEARER_TOKEN=$4
8+
SLACK_CHANNEL_ID=$5
9+
DRY_RUN=${6:-false} # Optional 6th parameter for dry run
10+
11+
if [ "$WARNING_COUNT" -eq 0 ]; then
12+
echo "No warning violations found, skipping ticket creation"
13+
exit 0
14+
fi
15+
16+
# Read violation details if available
17+
VIOLATION_DETAILS=""
18+
if [ -f "tools/spectral/ipa/metrics/outputs/warning-violations.json" ]; then
19+
VIOLATION_DETAILS=$(jq -r '
20+
group_by(.code) |
21+
map("• " + .[0].code + " (" + (length | tostring) + " violations)") |
22+
join("\n")
23+
' tools/spectral/ipa/metrics/outputs/warning-violations.json)
24+
fi
25+
26+
if [ "$DRY_RUN" = "true" ]; then
27+
echo "=== DRY RUN MODE ==="
28+
echo "Would create Jira ticket with:"
29+
echo "Summary: Warning-level IPA violations found - $WARNING_COUNT violations"
30+
echo "Description:"
31+
echo "Warning-level violations were found during IPA validation.
32+
33+
Violation Summary:
34+
$VIOLATION_DETAILS
35+
36+
Total violations: $WARNING_COUNT"
37+
echo ""
38+
echo "Would send Slack message:"
39+
SLACK_SUMMARY=$(echo "$VIOLATION_DETAILS" | head -3)
40+
if [ $(echo "$VIOLATION_DETAILS" | wc -l) -gt 3 ]; then
41+
SLACK_SUMMARY="$SLACK_SUMMARY\n... and more"
42+
fi
43+
echo "Warning-level IPA violations found ($WARNING_COUNT violations).
44+
45+
Top violations:
46+
$SLACK_SUMMARY
47+
48+
Jira ticket: [DRY RUN - no ticket created]"
49+
exit 0
50+
fi
51+
52+
# Check if warning ticket already exists
53+
EXISTING_TICKET=$(curl -s -H "Authorization: Bearer $JIRA_API_TOKEN" \
54+
"https://jira.mongodb.org/rest/api/2/search?jql=project=CLOUDP AND summary~'Warning-level IPA violations' AND status!=Done" \
55+
| jq -r '.issues[0].key // empty')
56+
57+
if [ -n "$EXISTING_TICKET" ]; then
58+
echo "Warning ticket already exists: $EXISTING_TICKET"
59+
exit 0
60+
fi
61+
62+
# Create detailed description
63+
DESCRIPTION="Warning-level violations were found during IPA validation. Please review and add exceptions if valid, or address false positives.
64+
65+
Violation Summary:
66+
$VIOLATION_DETAILS
67+
68+
Total violations: $WARNING_COUNT"
69+
70+
# Create new Jira ticket
71+
TICKET_RESPONSE=$(curl -s -X POST -H "Authorization: Bearer $JIRA_API_TOKEN" \
72+
-H "Content-Type: application/json" \
73+
-d "{
74+
\"fields\": {
75+
\"project\": {\"key\": \"CLOUDP\"},
76+
\"summary\": \"Warning-level IPA violations found - $WARNING_COUNT violations\",
77+
\"description\": \"$DESCRIPTION\",
78+
\"issuetype\": {\"name\": \"Task\"},
79+
\"assignee\": {\"id\": \"$TEAM_ID\"}
80+
}
81+
}" \
82+
"https://jira.mongodb.org/rest/api/2/issue/")
83+
84+
TICKET_KEY=$(echo "$TICKET_RESPONSE" | jq -r '.key')
85+
86+
if [ "$TICKET_KEY" != "null" ]; then
87+
echo "Created Jira ticket: $TICKET_KEY"
88+
89+
# Create summary for Slack
90+
SLACK_SUMMARY=""
91+
if [ -n "$VIOLATION_DETAILS" ]; then
92+
SLACK_SUMMARY=$(echo "$VIOLATION_DETAILS" | head -3)
93+
if [ $(echo "$VIOLATION_DETAILS" | wc -l) -gt 3 ]; then
94+
SLACK_SUMMARY="$SLACK_SUMMARY\n... and more"
95+
fi
96+
fi
97+
98+
# Send Slack notification with violation summary
99+
SLACK_MESSAGE="Warning-level IPA violations found ($WARNING_COUNT violations).
100+
101+
Top violations:
102+
$SLACK_SUMMARY
103+
104+
Jira ticket: https://jira.mongodb.org/browse/$TICKET_KEY"
105+
106+
curl -X POST -H "Authorization: Bearer $SLACK_BEARER_TOKEN" \
107+
-H "Content-type: application/json" \
108+
--data "{\"channel\":\"$SLACK_CHANNEL_ID\",\"text\":\"$SLACK_MESSAGE\"}" \
109+
https://slack.com/api/chat.postMessage
110+
else
111+
echo "Failed to create Jira ticket"
112+
exit 1
113+
fi

.github/workflows/release-IPA-metrics.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,16 @@ jobs:
3838
working-directory: ${{ github.workspace }}
3939

4040
- name: Run Metric Collection Job
41+
id: metric-collection
4142
working-directory: tools/spectral/ipa/metrics/scripts
42-
run: node runMetricCollection.js "${{ github.workspace }}/v2.json"
43+
run: |
44+
node runMetricCollection.js "${{ github.workspace }}/v2.json"
45+
if [ -f "../outputs/warning-count.txt" ]; then
46+
warning_count=$(cat ../outputs/warning-count.txt)
47+
echo "warning_count=${warning_count}" >> $GITHUB_OUTPUT
48+
else
49+
echo "warning_count=0" >> $GITHUB_OUTPUT
50+
fi
4351
4452
- name: aws configure
4553
uses: aws-actions/configure-aws-credentials@v4
@@ -54,6 +62,18 @@ jobs:
5462
working-directory: tools/spectral/ipa/metrics/scripts
5563
run: node dataDump.js
5664

65+
- name: Handle Warning Violations
66+
if: ${{ steps.metric-collection.outputs.warning_count > 0 }}
67+
env:
68+
WARNING_COUNT: ${{ steps.metric-collection.outputs.warning_count }}
69+
TEAM_ID: ${{ vars.JIRA_TEAM_ID_APIX_1 }}
70+
JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
71+
SLACK_BEARER_TOKEN: ${{ secrets.SLACK_BEARER_TOKEN }}
72+
SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID_APIX_1_DEV }}
73+
run: |
74+
chmod +x .github/scripts/handle_warning_violations.sh
75+
.github/scripts/handle_warning_violations.sh "$WARNING_COUNT" "$TEAM_ID" "$JIRA_API_TOKEN" "$SLACK_BEARER_TOKEN" "$SLACK_CHANNEL_ID"
76+
5777
failure-handler:
5878
name: Failure Handler
5979
needs: [ release-IPA-metrics ]

tools/spectral/ipa/__tests__/metrics/data/collector-results.log

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
{
2-
"violations": [],
2+
"violations": [
3+
{
4+
"componentId": "paths./api/atlas/v2/federationSettings/{federationSettingsId}/connectedOrgConfigs/{orgId}.get",
5+
"ruleName": "xgen-IPA-104-valid-operation-id"
6+
}],
37
"adoptions": [
48
{
59
"componentId": "paths./api/atlas/v2",

tools/spectral/ipa/__tests__/metrics/data/expected-metric-results.json

Lines changed: 1761 additions & 1 deletion
Large diffs are not rendered by default.

tools/spectral/ipa/__tests__/metrics/metricCollection.test.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,15 @@ describe('tools/spectral/ipa/metrics/metricCollection.js runMetricCollectionJob'
2424

2525
it('Outputs the expected metrics collection results', async () => {
2626
const expectedResults = JSON.parse(fs.readFileSync(expectedResultFilePath, 'utf8'));
27+
console.log(expectedResults[expectedResults.length-1]);
2728
const spectral = new Spectral();
2829

2930
const results = await runMetricCollectionJob(testConfig, spectral);
3031

3132
expect(results).not.toBe(undefined);
32-
expect(results.length).toEqual(expectedResults.length);
33+
expect(results.metrics.length).toEqual(expectedResults.length);
3334

34-
results.forEach((entry, index) => {
35+
results.metrics.forEach((entry, index) => {
3536
const expectedEntry = getEntry(expectedResults, entry['component_id'], entry['ipa_rule']);
3637
expect(entry['component_id']).toEqual(expectedEntry['component_id']);
3738
expect(entry['adoption_status']).toEqual(expectedEntry['adoption_status']);

tools/spectral/ipa/metrics/metricCollection.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export async function runMetricCollectionJob(
2222
console.log('Extracting team ownership data...');
2323
const ownershipData = extractTeamOwnership(oasContent);
2424

25-
console.log('Getting rule severities...');
25+
console.log(`Getting rule severities... ${rulesetFilePath}`);
2626
const ruleset = await loadRuleset(rulesetFilePath, spectral);
2727
const ruleSeverityMap = getSeverityPerRule(ruleset);
2828

@@ -32,8 +32,28 @@ export async function runMetricCollectionJob(
3232
console.log('Merging results...');
3333
const mergedResults = merge(ownershipData, collectorResults, ruleSeverityMap);
3434

35+
const warningViolations = mergedResults.filter(result =>
36+
result.severity_level === 1 && result.adoption_status === 'violated'
37+
);
38+
39+
const processedWarnings = warningViolations.map(violation => ({
40+
code: violation.ipa_rule,
41+
message: `IPA rule ${violation.ipa_rule} violated`,
42+
path: violation.component_id,
43+
source: null
44+
}));
45+
46+
console.log(`Found ${warningViolations.length} warning-level violations`);
47+
48+
3549
console.log('Metric collection job complete.');
36-
return mergedResults;
50+
return {
51+
metrics: mergedResults,
52+
warnings: {
53+
count: warningViolations.length,
54+
violations: processedWarnings
55+
}
56+
};
3757
} catch (error) {
3858
console.error('Error during metric collection:', error.message);
3959
throw error;

tools/spectral/ipa/metrics/scripts/runMetricCollection.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import fs from 'node:fs';
2+
import path from 'path';
23
import { spawnSync } from 'child_process';
34
import spectral from '@stoplight/spectral-core';
4-
import { Compression, Table, writeParquet, WriterPropertiesBuilder } from 'parquet-wasm';
5+
import {
6+
Compression,
7+
Table,
8+
writeParquet,
9+
WriterPropertiesBuilder,
10+
} from 'parquet-wasm';
511
import { tableFromJSON, tableToIPC } from 'apache-arrow';
612
import config from '../config.js';
713
import { runMetricCollectionJob } from '../metricCollection.js';
@@ -53,12 +59,21 @@ runMetricCollectionJob(
5359
)
5460
.then((results) => {
5561
console.log('Writing results');
56-
const table = tableFromJSON(results);
62+
const table = tableFromJSON(results.metrics);
5763
const wasmTable = Table.fromIPCStream(tableToIPC(table, 'stream'));
5864
const parquetUint8Array = writeParquet(
5965
wasmTable,
6066
new WriterPropertiesBuilder().setCompression(Compression.GZIP).build()
6167
);
6268
fs.writeFileSync(config.defaultMetricCollectionResultsFilePath, parquetUint8Array);
69+
fs.writeFileSync(
70+
path.join(config.defaultOutputsDir, 'warning-count.txt'),
71+
results.warnings.count.toString()
72+
);
73+
74+
fs.writeFileSync(
75+
path.join(config.defaultOutputsDir, 'warning-violations.json'),
76+
JSON.stringify(results.warnings.violations, null, 2)
77+
);
6378
})
6479
.catch((error) => console.error(error.message));

0 commit comments

Comments
 (0)