Skip to content

Commit bf9c013

Browse files
ci(ipa): automatic warning-level violation detection (#905)
1 parent 3b31e2f commit bf9c013

File tree

7 files changed

+1896
-8
lines changed

7 files changed

+1896
-8
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/bin/bash
2+
set -eou pipefail
3+
4+
if [ "${WARNING_COUNT}" -eq 0 ]; then
5+
echo "No warning violations found, skipping ticket creation"
6+
exit 0
7+
fi
8+
9+
# Read violation details if available
10+
VIOLATION_DETAILS=""
11+
if [ -f "tools/spectral/ipa/metrics/outputs/warning-violations.json" ]; then
12+
VIOLATION_DETAILS=$(jq -r '
13+
group_by(.code) |
14+
map("• " + .[0].code + " (" + (length | tostring) + " violations)") |
15+
join("\n")
16+
' tools/spectral/ipa/metrics/outputs/warning-violations.json)
17+
fi
18+
19+
# Check if warning ticket already exists
20+
EXISTING_TICKET=$(curl -s -H "Authorization: Bearer ${JIRA_API_TOKEN}" \
21+
"https://jira.mongodb.org/rest/api/2/search?jql=project=CLOUDP AND summary~'Warning-level IPA violations' AND status!=Done" \
22+
| jq -r '.issues[0].key // empty')
23+
24+
if [ -n "${EXISTING_TICKET}" ]; then
25+
echo "Warning ticket already exists: ${EXISTING_TICKET}"
26+
exit 0
27+
fi
28+
29+
# Create detailed description
30+
DESCRIPTION="Warning-level violations were found during IPA validation. Please review and add exceptions if valid, or address false positives.
31+
These warning-level checks are part of the rule rollout process. See the IPA Validation Technical Documentation for details:
32+
https://wiki.corp.mongodb.com/spaces/MMS/pages/315003555/IPA+Validation+Technical+Documentation+Runbook#IPAValidationTechnicalDocumentation%26Runbook-RolloutofNewRule
33+
34+
Violation Summary:
35+
${VIOLATION_DETAILS}
36+
37+
Total violations: ${WARNING_COUNT}"
38+
39+
# Create new Jira ticket
40+
TICKET_RESPONSE=$(curl -s -X POST -H "Authorization: Bearer ${JIRA_API_TOKEN}" \
41+
-H "Content-Type: application/json" \
42+
-d "{
43+
\"fields\": {
44+
\"project\": {\"key\": \"CLOUDP\"},
45+
\"summary\": \"Warning-level IPA violations found\",
46+
\"description\": \"${DESCRIPTION}\",
47+
\"issuetype\": {\"name\": \"Task\"},
48+
\"assignee\": {\"id\": \"${TEAM_ID}\"}
49+
}
50+
}" \
51+
"https://jira.mongodb.org/rest/api/2/issue/")
52+
53+
TICKET_KEY=$(echo "${TICKET_RESPONSE}" | jq -r '.key')
54+
55+
if [ "${TICKET_KEY}" != "null" ]; then
56+
echo "Created Jira ticket: ${TICKET_KEY}"
57+
58+
# Create summary for Slack
59+
SLACK_SUMMARY=""
60+
if [ -n "${VIOLATION_DETAILS}" ]; then
61+
SLACK_SUMMARY=$(echo "${VIOLATION_DETAILS}" | head -3)
62+
if [ "$(echo "${VIOLATION_DETAILS}" | wc -l)" -gt 3 ]; then
63+
SLACK_SUMMARY="${SLACK_SUMMARY}\n... and more"
64+
fi
65+
fi
66+
67+
# Send Slack notification with violation summary
68+
SLACK_MESSAGE="Warning-level IPA violations found (${WARNING_COUNT} violations) (${SLACK_ONCALL_USER}).
69+
70+
Jira ticket: https://jira.mongodb.org/browse/${TICKET_KEY}"
71+
72+
curl -X POST -H "Authorization: Bearer ${SLACK_BEARER_TOKEN}" \
73+
-H "Content-type: application/json" \
74+
--data "{\"channel\":\"${SLACK_CHANNEL_ID}\",\"text\":\"${SLACK_MESSAGE}\"}" \
75+
https://slack.com/api/chat.postMessage
76+
else
77+
echo "Failed to create Jira ticket"
78+
exit 1
79+
fi

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ jobs:
2323
tools/spectral/ipa
2424
package.json
2525
package-lock.json
26+
.github/scripts
2627
2728
- name: Setup Node
2829
uses: actions/setup-node@v4
@@ -38,8 +39,10 @@ jobs:
3839
working-directory: ${{ github.workspace }}
3940

4041
- name: Run Metric Collection Job
42+
id: metric-collection
4143
working-directory: tools/spectral/ipa/metrics/scripts
42-
run: node runMetricCollection.js "${{ github.workspace }}/v2.json"
44+
run: |
45+
node runMetricCollection.js "${{ github.workspace }}/v2.json"
4346
4447
- name: aws configure
4548
uses: aws-actions/configure-aws-credentials@v4
@@ -54,6 +57,18 @@ jobs:
5457
working-directory: tools/spectral/ipa/metrics/scripts
5558
run: node dataDump.js
5659

60+
# Enable this step in scope of CLOUDP-339852
61+
# - name: Handle Warning Violations
62+
# if: ${{ steps.metric-collection.outputs.warning_count > 0 }}
63+
# env:
64+
# WARNING_COUNT: ${{ steps.metric-collection.outputs.warning_count }}
65+
# TEAM_ID: ${{ vars.JIRA_TEAM_ID_APIX_PLATFORM }}
66+
# JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }}
67+
# SLACK_BEARER_TOKEN: ${{ secrets.SLACK_BEARER_TOKEN }}
68+
# SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL_ID_APIX_PLATFORM_DEV }}
69+
# SLACK_ONCALL_USER: ${{ secrets.SLACK_APIX_PLATFORM_ONCALL_USER }}
70+
# run: .github/scripts/handle_warning_violations.sh
71+
5772
failure-handler:
5873
name: Failure Handler
5974
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: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,8 @@ describe('tools/spectral/ipa/metrics/metricCollection.js runMetricCollectionJob'
2929
const results = await runMetricCollectionJob(testConfig, spectral);
3030

3131
expect(results).not.toBe(undefined);
32-
expect(results.length).toEqual(expectedResults.length);
33-
34-
results.forEach((entry, index) => {
32+
expect(results.metrics.length).toEqual(expectedResults.length);
33+
results.metrics.forEach((entry, index) => {
3534
const expectedEntry = getEntry(expectedResults, entry['component_id'], entry['ipa_rule']);
3635
expect(entry['component_id']).toEqual(expectedEntry['component_id']);
3736
expect(entry['adoption_status']).toEqual(expectedEntry['adoption_status']);
@@ -41,6 +40,14 @@ describe('tools/spectral/ipa/metrics/metricCollection.js runMetricCollectionJob'
4140
expect(entry['owner_team']).toEqual(expectedEntry['owner_team']);
4241
expect(entry['severity_level']).toEqual(expectedEntry['severity_level']);
4342
});
43+
44+
expect(results.warnings.count).toEqual(1);
45+
const violations = [
46+
{
47+
code: 'xgen-IPA-104-valid-operation-id',
48+
},
49+
];
50+
expect(results.warnings.violations).toEqual(violations);
4451
});
4552
});
4653

tools/spectral/ipa/metrics/metricCollection.js

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,24 @@ export async function runMetricCollectionJob(
3232
console.log('Merging results...');
3333
const mergedResults = merge(ownershipData, collectorResults, ruleSeverityMap);
3434

35+
const warningViolations = mergedResults.filter(
36+
(result) => result.severity_level === 'warn' && result.adoption_status === 'violated'
37+
);
38+
39+
const processedWarnings = warningViolations.map((violation) => ({
40+
code: violation.ipa_rule,
41+
}));
42+
43+
console.log(`Found ${warningViolations.length} warning-level violations`);
44+
3545
console.log('Metric collection job complete.');
36-
return mergedResults;
46+
return {
47+
metrics: mergedResults,
48+
warnings: {
49+
count: warningViolations.length,
50+
violations: processedWarnings,
51+
},
52+
};
3753
} catch (error) {
3854
console.error('Error during metric collection:', error.message);
3955
throw error;

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import fs from 'node:fs';
2+
import path from 'path';
23
import { spawnSync } from 'child_process';
34
import spectral from '@stoplight/spectral-core';
45
import { Compression, Table, writeParquet, WriterPropertiesBuilder } from 'parquet-wasm';
@@ -53,12 +54,18 @@ runMetricCollectionJob(
5354
)
5455
.then((results) => {
5556
console.log('Writing results');
56-
const table = tableFromJSON(results);
57+
const table = tableFromJSON(results.metrics);
5758
const wasmTable = Table.fromIPCStream(tableToIPC(table, 'stream'));
5859
const parquetUint8Array = writeParquet(
5960
wasmTable,
6061
new WriterPropertiesBuilder().setCompression(Compression.GZIP).build()
6162
);
6263
fs.writeFileSync(config.defaultMetricCollectionResultsFilePath, parquetUint8Array);
64+
fs.writeFileSync(path.join(config.defaultOutputsDir, 'warning-count.txt'), results.warnings.count.toString());
65+
66+
fs.writeFileSync(
67+
path.join(config.defaultOutputsDir, 'warning-violations.json'),
68+
JSON.stringify(results.warnings.violations, null, 2)
69+
);
6370
})
6471
.catch((error) => console.error(error.message));

0 commit comments

Comments
 (0)