From 39fea292b95b758d276bf6a6aad302f2aa05c8dc Mon Sep 17 00:00:00 2001 From: Mike McNeil Date: Wed, 27 Jul 2022 00:36:31 -0500 Subject: [PATCH] Tool: Bring back mergefreeze API integration (#6905) * Tool: Bring back mergefreeze API integration Context: https://github.com/fleetdm/fleet/pull/5628#issuecomment-1196175485 Unfortunately, the API doesn't work. * Lay out how this would work in the database - but instead, do it ephemerally for now * Remove model * Maintain state (the easy way for now) --- .../webhooks/receive-from-github.js | 84 +++++++++++-- website/assets/.eslintrc | 5 +- website/scripts/freeze-open-pull-requests.js | 116 +++++++++--------- 3 files changed, 142 insertions(+), 63 deletions(-) diff --git a/website/api/controllers/webhooks/receive-from-github.js b/website/api/controllers/webhooks/receive-from-github.js index 51f11d640..8f69898cc 100644 --- a/website/api/controllers/webhooks/receive-from-github.js +++ b/website/api/controllers/webhooks/receive-from-github.js @@ -27,7 +27,13 @@ module.exports = { let GitHub = require('machinepack-github'); - let IS_FROZEN = false;// « Set this to `true` whenever a freeze is in effect, then set it back to `false` when the freeze ends. + // Is this in use? + // > For context on the history of this bit of code, which has gone been + // > implemented a couple of different ways, and gone back and forth, check out: + // > https://github.com/fleetdm/fleet/pull/5628#issuecomment-1196175485 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // let IS_FROZEN = false;// « Set this to `true` whenever a freeze is in effect, then set it back to `false` when the freeze ends. + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - let GITHUB_USERNAMES_OF_BOTS_AND_MAINTAINERS = [// « Used in multiple places below. 'sailsbot', @@ -71,6 +77,10 @@ module.exports = { let GITHUB_USERNAME_OF_DRI_FOR_LABELS = 'noahtalerman';// « Used below (FUTURE: Remove this capability as Fleet has outgrown it. 2022-05-05) + if (!sails.config.custom.mergeFreezeAccessToken) { + throw new Error('An access token for the MergeFreeze API (sails.config.custom.mergeFreezeAccessToken) is required to enable automated unfreezing/freezing of changes based on the files they change. Please ask for help in #g-digital-experience, whether you are testing locally or using this as a live webhook.'); + } + if (!sails.config.custom.slackWebhookUrlForGithubBot) { throw new Error('No Slack webhook URL configured for the GitHub bot to notify with alerts! (Please set `sails.config.custom.slackWebhookUrlForGithubBot`.)'); }//• @@ -218,18 +228,78 @@ module.exports = { isGithubUserMaintainerOrDoesntMatter: GITHUB_USERNAMES_OF_BOTS_AND_MAINTAINERS.includes(sender.login.toLowerCase()) }); + // Check whether the "main" branch is currently frozen (i.e. a feature freeze) + // [?] https://docs.mergefreeze.com/web-api#get-freeze-status + let isMainBranchFrozen = (await sails.helpers.http.get('https://www.mergefreeze.com/api/branches/fleetdm/fleet/main', { access_token: sails.config.custom.mergeFreezeAccessToken })).frozen;//eslint-disable-line camelcase + // Now, if appropriate, auto-approve the change. if (isAutoApproved) { // [?] https://docs.github.com/en/rest/reference/pulls#create-a-review-for-a-pull-request await sails.helpers.http.post(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews`, { event: 'APPROVE' }, baseHeaders); - } else if (IS_FROZEN) { - // [?] https://docs.github.com/en/rest/reference/pulls#create-a-review-for-a-pull-request - await sails.helpers.http.post(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews`, { - event: 'REQUEST_CHANGES', - body: 'The repository is currently frozen for an upcoming release. \n> After the freeze has ended, please dismiss this review. \n\nIn case of emergency, you can dismiss this review and merge now.' - }, baseHeaders); + + // Since we're only using a single instance, and because the worst case scenario is that we refreeze some + // all-markdown PRs that had already been frozen, instead of using the database, we'll just use a little + // in-memory pocket here of PRs seen by this instance of the Sails app. To get around any issues with this, + // users can edit and resave the PR description to trigger their PR to be unfrozen. + // FUTURE: Go through the trouble to migrate the database and make a little Platform model to hold this state in. + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // Grab the set of GitHub pull request numbers the bot considers "unfrozen". + sails.pocketOfPrNumbersUnfrozen = sails.pocketOfPrNumbersUnfrozen || []; + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + // If "main" is explicitly frozen, then unfreeze this PR because it no longer contains + // (or maybe never did contain) changes to freezeworthy files. + if (isMainBranchFrozen) { + + sails.pocketOfPrNumbersUnfrozen = _.union(sails.pocketOfPrNumbersUnfrozen, [ prNumber ]); + + // [?] See May 6th, 2022 changelog, which includes this code sample: + // (https://www.mergefreeze.com/news) + // (but as of July 26, 2022, I didn't see it documented here: https://docs.mergefreeze.com/web-api#post-freeze-status) + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // > You may now freeze or unfreeze a single PR (while maintaining the overall freeze) via the Web API. + // ``` + // curl -d "frozen=true&user_name=Scooby Doo&unblocked_prs=[3]" -X POST https://www.mergefreeze.com/api/branches/mergefreeze/core/master/?access_token=[Your access token] + // ``` + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + await sails.helpers.http.post(`https://www.mergefreeze.com/api/branches/fleetdm/fleet/main?access_token=${encodeURIComponent(sails.config.custom.mergeFreezeAccessToken)}`, { + frozen: true, + user_name: 'fleet-release',//eslint-disable-line camelcase + unblocked_prs: sails.pocketOfPrNumbersUnfrozen,//eslint-disable-line camelcase + }); + + }//fi + + } else { + // If "main" is explicitly frozen, then freeze this PR because it now contains + // (or maybe always did contain) changes to freezeworthy files. + if (isMainBranchFrozen) { + + sails.pocketOfPrNumbersUnfrozen = _.difference(sails.pocketOfPrNumbersUnfrozen, [ prNumber ]); + + // [?] See explanation above. + await sails.helpers.http.post(`https://www.mergefreeze.com/api/branches/fleetdm/fleet/main?access_token=${encodeURIComponent(sails.config.custom.mergeFreezeAccessToken)}`, { + frozen: true, + user_name: 'fleet-release',//eslint-disable-line camelcase + unblocked_prs: sails.pocketOfPrNumbersUnfrozen,//eslint-disable-line camelcase + }); + }//fi + + // Is this in use? + // > For context on the history of this bit of code, which has gone been + // > implemented a couple of different ways, and gone back and forth, check out: + // > https://github.com/fleetdm/fleet/pull/5628#issuecomment-1196175485 + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // if (IS_FROZEN) { + // // [?] https://docs.github.com/en/rest/reference/pulls#create-a-review-for-a-pull-request + // await sails.helpers.http.post(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews`, { + // event: 'REQUEST_CHANGES', + // body: 'The repository is currently frozen for an upcoming release. \n> After the freeze has ended, please dismiss this review. \n\nIn case of emergency, you can dismiss this review and merge now.' + // }, baseHeaders); + // }//fi + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } } diff --git a/website/assets/.eslintrc b/website/assets/.eslintrc index 7731ddd95..36be26e36 100644 --- a/website/assets/.eslintrc +++ b/website/assets/.eslintrc @@ -55,7 +55,10 @@ // Make sure backend globals aren't indadvertently tolerated in our client-side JS: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - "sails": false, - "User": false + "User": false, + "HistoricalUsageSnapshot": false, + "Quote": false, + "Subscription": false, // ...and any other backend globals (e.g. `"Organization": false`) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - } diff --git a/website/scripts/freeze-open-pull-requests.js b/website/scripts/freeze-open-pull-requests.js index c86cd4176..c8035d627 100644 --- a/website/scripts/freeze-open-pull-requests.js +++ b/website/scripts/freeze-open-pull-requests.js @@ -1,6 +1,5 @@ module.exports = { - friendlyName: 'Freeze open pull requests', @@ -22,62 +21,69 @@ module.exports = { }, - fn: async function ({dry: isDryRun, limit: maxNumPullRequestsToCheck }) { - - sails.log('Running custom shell script... (`sails run freeze-open-pull-requests`)'); - - let owner = 'fleetdm'; - let repo = 'fleet'; - let baseHeaders = { - 'User-Agent': 'sails run freeze-open-pull-requests', - 'Authorization': `token ${sails.config.custom.githubAccessToken}` - }; - - // Fetch open pull requests - // [?] https://docs.github.com/en/rest/pulls/pulls#list-pull-requests - let openPullRequests = await sails.helpers.http.get(`https://api.github.com/repos/${owner}/${repo}/pulls`, { - state: 'open', - per_page: maxNumPullRequestsToCheck,//eslint-disable-line camelcase - }, baseHeaders); - - if (openPullRequests.length > maxNumPullRequestsToCheck) { - openPullRequests = openPullRequests.slice(0,maxNumPullRequestsToCheck); - } - - let SECONDS_TO_WAIT = 5; - sails.log(`Examining and potentially freezing ${openPullRequests.length} PRs very soon… (To cancel, press CTRL+C quickly within ${SECONDS_TO_WAIT}s!)`); - await sails.helpers.flow.pause(SECONDS_TO_WAIT*1000); - - // For all open pull requests… - await sails.helpers.flow.simultaneouslyForEach(openPullRequests, async(pullRequest)=>{ - - let prNumber = pullRequest.number; - let prAuthor = pullRequest.user.login; - require('assert')(prAuthor !== undefined); - - // Freeze, if appropriate. - // (Check versus the intersection of DRIs for all changed files to make sure SOMEONE is preapproved for all of them.) - let isAuthorPreapproved = await sails.helpers.githubAutomations.getIsPrPreapproved.with({ - prNumber: prNumber, - isGithubUserMaintainerOrDoesntMatter: true// « doesn't matter here because no auto-approval is happening. Worst case, a community PR to an area with a "*" in the DRI mapping remains unfrozen. - }); - - if (isDryRun) { - sails.log(`#${prNumber} by @${prAuthor}:`, isAuthorPreapproved ? 'Would have skipped freeze…' : 'Would have frozen…'); - } else { - sails.log(`#${prNumber} by @${prAuthor}:`, isAuthorPreapproved ? 'Skipping freeze…' : 'Freezing…'); - if (!isAuthorPreapproved) { - // [?] https://docs.github.com/en/rest/reference/pulls#create-a-review-for-a-pull-request - await sails.helpers.http.post(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews`, { - event: 'REQUEST_CHANGES', - body: 'The repository has been frozen for an upcoming release. In case of emergency, you can dismiss this review and merge.' - }, baseHeaders); - }//fi - } - }); - + fn: async function () { + // Is this in use? + // > For context on the history of this bit of code, which has gone been + // > implemented a couple of different ways, and gone back and forth, check out: + // > https://github.com/fleetdm/fleet/pull/5628#issuecomment-1196175485 + throw new Error('Not currently in use. See comments in this script for more information.'); } + // fn: async function ({dry: isDryRun, limit: maxNumPullRequestsToCheck }) { + + // sails.log('Running custom shell script... (`sails run freeze-open-pull-requests`)'); + + // let owner = 'fleetdm'; + // let repo = 'fleet'; + // let baseHeaders = { + // 'User-Agent': 'sails run freeze-open-pull-requests', + // 'Authorization': `token ${sails.config.custom.githubAccessToken}` + // }; + + // // Fetch open pull requests + // // [?] https://docs.github.com/en/rest/pulls/pulls#list-pull-requests + // let openPullRequests = await sails.helpers.http.get(`https://api.github.com/repos/${owner}/${repo}/pulls`, { + // state: 'open', + // per_page: maxNumPullRequestsToCheck,//eslint-disable-line camelcase + // }, baseHeaders); + + // if (openPullRequests.length > maxNumPullRequestsToCheck) { + // openPullRequests = openPullRequests.slice(0,maxNumPullRequestsToCheck); + // } + + // let SECONDS_TO_WAIT = 5; + // sails.log(`Examining and potentially freezing ${openPullRequests.length} PRs very soon… (To cancel, press CTRL+C quickly within ${SECONDS_TO_WAIT}s!)`); + // await sails.helpers.flow.pause(SECONDS_TO_WAIT*1000); + + // // For all open pull requests… + // await sails.helpers.flow.simultaneouslyForEach(openPullRequests, async(pullRequest)=>{ + + // let prNumber = pullRequest.number; + // let prAuthor = pullRequest.user.login; + // require('assert')(prAuthor !== undefined); + + // // Freeze, if appropriate. + // // (Check versus the intersection of DRIs for all changed files to make sure SOMEONE is preapproved for all of them.) + // let isAuthorPreapproved = await sails.helpers.githubAutomations.getIsPrPreapproved.with({ + // prNumber: prNumber, + // isGithubUserMaintainerOrDoesntMatter: true// « doesn't matter here because no auto-approval is happening. Worst case, a community PR to an area with a "*" in the DRI mapping remains unfrozen. + // }); + + // if (isDryRun) { + // sails.log(`#${prNumber} by @${prAuthor}:`, isAuthorPreapproved ? 'Would have skipped freeze…' : 'Would have frozen…'); + // } else { + // sails.log(`#${prNumber} by @${prAuthor}:`, isAuthorPreapproved ? 'Skipping freeze…' : 'Freezing…'); + // if (!isAuthorPreapproved) { + // // [?] https://docs.github.com/en/rest/reference/pulls#create-a-review-for-a-pull-request + // await sails.helpers.http.post(`https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/reviews`, { + // event: 'REQUEST_CHANGES', + // body: 'The repository has been frozen for an upcoming release. In case of emergency, you can dismiss this review and merge.' + // }, baseHeaders); + // }//fi + // } + // }); + + // } };