From 23e9569dd4e159531de1cc829a94d99d2b558055 Mon Sep 17 00:00:00 2001 From: bretg Date: Tue, 7 Jan 2025 09:14:48 -0500 Subject: [PATCH] GitHub: codepath alerts (#3645) --- .github/workflows/code-path-changes.yml | 34 +++++ .../workflows/scripts/codepath-notification | 19 +++ .../scripts/send-notification-on-change.js | 139 ++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 .github/workflows/code-path-changes.yml create mode 100644 .github/workflows/scripts/codepath-notification create mode 100644 .github/workflows/scripts/send-notification-on-change.js diff --git a/.github/workflows/code-path-changes.yml b/.github/workflows/code-path-changes.yml new file mode 100644 index 00000000000..8d180480538 --- /dev/null +++ b/.github/workflows/code-path-changes.yml @@ -0,0 +1,34 @@ +name: Notify Code Path Changes + +on: + pull_request: + types: [opened, synchronize] + paths: + - '**' + +env: + OAUTH2_CLIENT_ID: ${{ secrets.OAUTH2_CLIENT_ID }} + OAUTH2_CLIENT_SECRET: ${{ secrets.OAUTH2_CLIENT_SECRET }} + OAUTH2_REFRESH_TOKEN: ${{ secrets.OAUTH2_REFRESH_TOKEN }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + notify: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install dependencies + run: npm install axios nodemailer + + - name: Run Notification Script + run: | + node .github/workflows/scripts/send-notification-on-change.js diff --git a/.github/workflows/scripts/codepath-notification b/.github/workflows/scripts/codepath-notification new file mode 100644 index 00000000000..7665b800901 --- /dev/null +++ b/.github/workflows/scripts/codepath-notification @@ -0,0 +1,19 @@ +# when a changed file paths matches the regex, send an alert email +# structure of the file is: +# +# javascriptRegex : email address +# +# For example, in PBS Java, there are many paths that can belong to bid adapter: +# +# /src/main/java/org/prebid/server/bidder/BIDDER +# /src/main/resources/static/bidder-params/BIDDER.json +# /src/main/resources/bidder-config/BIDDER.yaml +# /src//main/java/org/prebid/server/proto/openrtb/ext/request/BIDDER +# /src/test/resources/org/prebid/server/it/openrtb2/BIDDER +# /src/test/java/org/prebid/server/it/BIDDERTest.java +# /src/test/java/org/prebid/server/bidder/BIDDER +# /src/main/java/org/prebid/server/spring/config/bidder/BIDDERConfiguration.java +# +# The aim is to find a minimal set of regex patterns that matches any file in these paths + +/ix|Ix|ix.json|ix.yaml: pdu-supply-prebid@indexexchange.com diff --git a/.github/workflows/scripts/send-notification-on-change.js b/.github/workflows/scripts/send-notification-on-change.js new file mode 100644 index 00000000000..f4e4fdcd3ca --- /dev/null +++ b/.github/workflows/scripts/send-notification-on-change.js @@ -0,0 +1,139 @@ +// send-notification-on-change.js +// +// called by the code-path-changes.yml workflow, this script queries github for +// the changes in the current PR, checkes the config file for whether any of those +// file paths are set to alert an email address, and sends email to multiple +// parties if needed + +const fs = require('fs'); +const path = require('path'); +const axios = require('axios'); +const nodemailer = require('nodemailer'); + +async function getAccessToken(clientId, clientSecret, refreshToken) { + try { + const response = await axios.post('https://oauth2.googleapis.com/token', { + client_id: clientId, + client_secret: clientSecret, + refresh_token: refreshToken, + grant_type: 'refresh_token', + }); + return response.data.access_token; + } catch (error) { + console.error('Failed to fetch access token:', error.response?.data || error.message); + process.exit(1); + } +} + +(async () => { + const configFilePath = path.join(__dirname, 'codepath-notification'); + const repo = process.env.GITHUB_REPOSITORY; + const prNumber = process.env.GITHUB_PR_NUMBER; + const token = process.env.GITHUB_TOKEN; + + // Generate OAuth2 access token + const clientId = process.env.OAUTH2_CLIENT_ID; + const clientSecret = process.env.OAUTH2_CLIENT_SECRET; + const refreshToken = process.env.OAUTH2_REFRESH_TOKEN; + + // validate params + if (!repo || !prNumber || !token || !clientId || !clientSecret || !refreshToken) { + console.error('Missing required environment variables.'); + process.exit(1); + } + + // the whole process is in a big try/catch. e.g. if the config file doesn't exist, github is down, etc. + try { + // Read and process the configuration file + const configFileContent = fs.readFileSync(configFilePath, 'utf-8'); + const configRules = configFileContent + .split('\n') + .filter(line => line.trim() !== '' && !line.trim().startsWith('#')) // Ignore empty lines and comments + .map(line => { + const [regex, email] = line.split(':').map(part => part.trim()); + return { regex: new RegExp(regex), email }; + }); + + // Fetch changed files from github + const [owner, repoName] = repo.split('/'); + const apiUrl = `https://api.github.com/repos/${owner}/${repoName}/pulls/${prNumber}/files`; + const response = await axios.get(apiUrl, { + headers: { + Authorization: `Bearer ${token}`, + Accept: 'application/vnd.github.v3+json', + }, + }); + + const changedFiles = response.data.map(file => file.filename); + console.log('Changed files:', changedFiles); + + // match file pathnames that are in the config and group them by email address + const matchesByEmail = {}; + changedFiles.forEach(file => { + configRules.forEach(rule => { + if (rule.regex.test(file)) { + if (!matchesByEmail[rule.email]) { + matchesByEmail[rule.email] = []; + } + matchesByEmail[rule.email].push(file); + } + }); + }); + + // Exit successfully if no matches were found + if (Object.keys(matchesByEmail).length === 0) { + console.log('No matches found. Exiting successfully.'); + process.exit(0); + } + + console.log('Grouped matches by email:', matchesByEmail); + + // get ready to email the changes + const accessToken = await getAccessToken(clientId, clientSecret, refreshToken); + + // Configure Nodemailer with OAuth2 + // service: 'Gmail', + const transporter = nodemailer.createTransport({ + host: "smtp.gmail.com", + port: 465, + secure: true, + auth: { + type: 'OAuth2', + user: 'info@prebid.org', + clientId: clientId, + clientSecret: clientSecret, + refreshToken: refreshToken, + accessToken: accessToken + }, + }); + + // Send one email per recipient + for (const [email, files] of Object.entries(matchesByEmail)) { + const emailBody = ` + ${email}, +

+ Files owned by you have been changed in open source ${repo}. The pull request is #${prNumber}. These are the files you own that have been modified: +

+ `; + + try { + await transporter.sendMail({ + from: `"Prebid Info" `, + to: email, + subject: `Files have been changed in open source ${repo}`, + html: emailBody, + }); + + console.log(`Email sent successfully to ${email}`); + console.log(`${emailBody}`); + } catch (error) { + console.error(`Failed to send email to ${email}:`, error.message); + } + } + } catch (error) { + console.error('Error:', error.message); + process.exit(1); + } +})();