Skip to content

Commit 1964dc7

Browse files
authored
Add codepath alerts (#4141)
1 parent 93d6bdd commit 1964dc7

File tree

3 files changed

+188
-0
lines changed

3 files changed

+188
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: Notify Code Path Changes
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize]
6+
paths:
7+
- '**'
8+
9+
env:
10+
OAUTH2_CLIENT_ID: ${{ secrets.OAUTH2_CLIENT_ID }}
11+
OAUTH2_CLIENT_SECRET: ${{ secrets.OAUTH2_CLIENT_SECRET }}
12+
OAUTH2_REFRESH_TOKEN: ${{ secrets.OAUTH2_REFRESH_TOKEN }}
13+
GITHUB_REPOSITORY: ${{ github.repository }}
14+
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
15+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16+
17+
jobs:
18+
notify:
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout Code
22+
uses: actions/checkout@v3
23+
24+
- name: Set up Node.js
25+
uses: actions/setup-node@v3
26+
with:
27+
node-version: '18'
28+
29+
- name: Install dependencies
30+
run: npm install axios nodemailer
31+
32+
- name: Run Notification Script
33+
run: |
34+
node .github/workflows/scripts/send-notification-on-change.js
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# when a changed file paths matches the regex, send an alert email
2+
# structure of the file is:
3+
#
4+
# javascriptRegex : email address
5+
#
6+
# For example, in PBS Go, there are many paths that can belong to bid adapter:
7+
#
8+
# /adapters/BIDDERCODE
9+
# /openrtb_ext/imp_BIDDERCODE.go
10+
# /static/bidder-params/BIDDERCODE.json
11+
# /static/bidder-info/BIDDERCODE.yaml
12+
#
13+
# The aim is to find a minimal set of regex patterns that matches any file in these paths
14+
15+
rubicon: header-bidding@magnite.com
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// send-notification-on-change.js
2+
//
3+
// called by the code-path-changes.yml workflow, this script queries github for
4+
// the changes in the current PR, checks the config file for whether any of those
5+
// file paths are set to alert an email address, and sends email to multiple
6+
// parties if needed
7+
8+
const fs = require('fs');
9+
const path = require('path');
10+
const axios = require('axios');
11+
const nodemailer = require('nodemailer');
12+
13+
async function getAccessToken(clientId, clientSecret, refreshToken) {
14+
try {
15+
const response = await axios.post('https://oauth2.googleapis.com/token', {
16+
client_id: clientId,
17+
client_secret: clientSecret,
18+
refresh_token: refreshToken,
19+
grant_type: 'refresh_token',
20+
});
21+
return response.data.access_token;
22+
} catch (error) {
23+
console.error('Failed to fetch access token:', error.response?.data || error.message);
24+
process.exit(1);
25+
}
26+
}
27+
28+
(async () => {
29+
const configFilePath = path.join(__dirname, 'codepath-notification');
30+
const repo = process.env.GITHUB_REPOSITORY;
31+
const prNumber = process.env.GITHUB_PR_NUMBER;
32+
const token = process.env.GITHUB_TOKEN;
33+
34+
// Generate OAuth2 access token
35+
const clientId = process.env.OAUTH2_CLIENT_ID;
36+
const clientSecret = process.env.OAUTH2_CLIENT_SECRET;
37+
const refreshToken = process.env.OAUTH2_REFRESH_TOKEN;
38+
39+
// validate params
40+
if (!repo || !prNumber || !token || !clientId || !clientSecret || !refreshToken) {
41+
console.error('Missing required environment variables.');
42+
process.exit(1);
43+
}
44+
45+
// the whole process is in a big try/catch. e.g. if the config file doesn't exist, github is down, etc.
46+
try {
47+
// Read and process the configuration file
48+
const configFileContent = fs.readFileSync(configFilePath, 'utf-8');
49+
const configRules = configFileContent
50+
.split('\n')
51+
.filter(line => line.trim() !== '' && !line.trim().startsWith('#')) // Ignore empty lines and comments
52+
.map(line => {
53+
const [regex, email] = line.split(':').map(part => part.trim());
54+
return { regex: new RegExp(regex), email };
55+
});
56+
57+
// Fetch changed files from github
58+
const [owner, repoName] = repo.split('/');
59+
const apiUrl = `https://api.github.com/repos/${owner}/${repoName}/pulls/${prNumber}/files`;
60+
const response = await axios.get(apiUrl, {
61+
headers: {
62+
Authorization: `Bearer ${token}`,
63+
Accept: 'application/vnd.github.v3+json',
64+
},
65+
});
66+
67+
const changedFiles = response.data.map(file => file.filename);
68+
console.log('Changed files:', changedFiles);
69+
70+
// match file pathnames that are in the config and group them by email address
71+
const matchesByEmail = {};
72+
changedFiles.forEach(file => {
73+
configRules.forEach(rule => {
74+
if (rule.regex.test(file)) {
75+
if (!matchesByEmail[rule.email]) {
76+
matchesByEmail[rule.email] = [];
77+
}
78+
matchesByEmail[rule.email].push(file);
79+
}
80+
});
81+
});
82+
83+
// Exit successfully if no matches were found
84+
if (Object.keys(matchesByEmail).length === 0) {
85+
console.log('No matches found. Exiting successfully.');
86+
process.exit(0);
87+
}
88+
89+
console.log('Grouped matches by email:', matchesByEmail);
90+
91+
// get ready to email the changes
92+
const accessToken = await getAccessToken(clientId, clientSecret, refreshToken);
93+
94+
// Configure Nodemailer with OAuth2
95+
// service: 'Gmail',
96+
const transporter = nodemailer.createTransport({
97+
host: "smtp.gmail.com",
98+
port: 465,
99+
secure: true,
100+
auth: {
101+
type: 'OAuth2',
102+
user: 'info@prebid.org',
103+
clientId: clientId,
104+
clientSecret: clientSecret,
105+
refreshToken: refreshToken,
106+
accessToken: accessToken
107+
},
108+
});
109+
110+
// Send one email per recipient
111+
for (const [email, files] of Object.entries(matchesByEmail)) {
112+
const emailBody = `
113+
${email},
114+
<p>
115+
Files owned by you have been changed in open source ${repo}. The <a href="https://github.com/${repo}/pull/${prNumber}">pull request is #${prNumber}</a>. These are the files you own that have been modified:
116+
<ul>
117+
${files.map(file => `<li>${file}</li>`).join('')}
118+
</ul>
119+
`;
120+
121+
try {
122+
await transporter.sendMail({
123+
from: `"Prebid Info" <info@prebid.org>`,
124+
to: email,
125+
subject: `Files have been changed in open source ${repo}`,
126+
html: emailBody,
127+
});
128+
129+
console.log(`Email sent successfully to ${email}`);
130+
console.log(`${emailBody}`);
131+
} catch (error) {
132+
console.error(`Failed to send email to ${email}:`, error.message);
133+
}
134+
}
135+
} catch (error) {
136+
console.error('Error:', error.message);
137+
process.exit(1);
138+
}
139+
})();

0 commit comments

Comments
 (0)