Skip to content

Commit

Permalink
MWPW-148050: auto merge stage to main (#2290)
Browse files Browse the repository at this point in the history
* mwpw-148050: auto merge PRs to main

* Expand condition

* Add PR feedback

* Address PR feedback
  • Loading branch information
mokimo authored Jun 3, 2024
1 parent e673183 commit 0c0a36b
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 53 deletions.
62 changes: 61 additions & 1 deletion .github/workflows/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,60 @@
const owner = process.env.REPO_OWNER || ''; // example owner: adobecom
const repo = process.env.REPO_NAME || ''; // example repo name: milo
const auth = process.env.GH_TOKEN || ''; // https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens
const CURRENT_YEAR = 2024;
const RCPDates = [
{
start: new Date('2024-05-26T00:00:00-07:00'),
end: new Date('2024-06-01T00:00:00-07:00'),
},
{
start: new Date('2024-06-13T11:00:00-07:00'),
end: new Date('2024-06-13T14:00:00-07:00'),
},
{
start: new Date('2024-06-30T00:00:00-07:00'),
end: new Date('2024-07-06T00:00:00-07:00'),
},
{
start: new Date('2024-08-25T00:00:00-07:00'),
end: new Date('2024-08-31T00:00:00-07:00'),
},
{
start: new Date('2024-09-12T11:00:00-07:00'),
end: new Date('2024-09-12T14:00:00-07:00'),
},
{
start: new Date('2024-10-14T00:00:00-07:00'),
end: new Date('2024-11-18T17:00:00-08:00'),
},
{
start: new Date('2024-11-17T00:00:00-08:00'),
end: new Date('2024-11-30T00:00:00-08:00'),
},
{
start: new Date('2024-12-12T11:00:00-08:00'),
end: new Date('2024-12-12T14:00:00-08:00'),
},
{
start: new Date('2024-12-15T00:00:00-08:00'),
end: new Date('2025-01-02T00:00:00-08:00'),
},
];

const isWithinRCP = () => {
const now = new Date();
if (now.getFullYear() !== CURRENT_YEAR) {
console.log(`ADD NEW RCPs for ${CURRENT_YEAR + 1}`);
return true;
}

if (RCPDates.some(({ start, end }) => start <= now && now <= end)) {
console.log('Current date is within a RCP. Stopping execution.');
return true;
}

return false;
};

const getLocalConfigs = () => {
if (!owner || !repo || !auth) {
Expand All @@ -12,7 +66,12 @@ Then run: node --env-file=.env .github/workflows/update-ims.js`);

const { Octokit } = require('@octokit/rest');
return {
github: { rest: new Octokit({ auth }) },
github: {
rest: new Octokit({ auth }),
repos: {
createDispatchEvent: () => console.log('local mock createDispatch'),
},
},
context: {
repo: {
owner,
Expand Down Expand Up @@ -83,4 +142,5 @@ module.exports = {
getLocalConfigs,
slackNotification,
pulls: { addLabels, addFiles, getChecks, getReviews },
isWithinRCP,
};
4 changes: 2 additions & 2 deletions .github/workflows/mark-stale-prs.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: "Close stale pull requests"
name: Close stale pull requests
on:
schedule:
- cron: "0 0 * * *"
- cron: '0 0 * * *'
workflow_dispatch:

jobs:
Expand Down
77 changes: 77 additions & 0 deletions .github/workflows/merge-to-main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
const {

Check warning on line 1 in .github/workflows/merge-to-main.js

View workflow job for this annotation

GitHub Actions / Running eslint

[eslint] reported by reviewdog 🐶 File ignored by default. Use a negated ignore pattern (like "--ignore-pattern '!<relative/path/to/filename>'") to override. Raw Output: {"fatal":false,"severity":1,"message":"File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override."}
slackNotification,
getLocalConfigs,
isWithinRCP,
pulls: { addLabels, addFiles, getChecks, getReviews },
} = require('./helpers.js');

// Run from the root of the project for local testing: node --env-file=.env .github/workflows/merge-to-main.js
const PR_TITLE = '[Release] Stage to Main';
const STAGE = 'stage';
const PROD = 'main';

let github, owner, repo;

const getStageToMainPR = () =>
github.rest.pulls
.list({ owner, repo, state: 'open', base: PROD, head: STAGE })
.then(({ data } = {}) => data.find(({ title } = {}) => title === PR_TITLE))
.then((pr) => pr && addLabels({ pr, github, owner, repo }));

const workingHours = () => {
const now = new Date();
const day = now.getUTCDay();
const hour = now.getUTCHours();
const isSunday = day === 0;
const isSaturday = day === 6;
const isFriday = day === 5;
return hour >= 8 && hour <= 20 && !isFriday && !isSaturday && !isSunday;
};

const main = async (params) => {
github = params.github;
owner = params.context.repo.owner;
repo = params.context.repo.repo;

if (isWithinRCP()) return console.log('Stopped, within RCP period.');
if (!workingHours()) return console.log('Stopped, outside working hours.');

try {
const stageToMainPR = await getStageToMainPR();
const signOffs = stageToMainPR?.labels.filter((l) => l.includes('SOT'));
console.log(`${signOffs.length} SOT labels on PR ${stageToMainPR.number}`);
if (signOffs.length >= 4) {
console.log('Stage to Main PR has all required labels. Merging...');
await github.rest.pulls.merge({
owner,
repo,
pull_number: stageToMainPR.number,
merge_method: 'merge',
});

await slackNotification(
`:rocket: Production release <${stageToMainPR.html_url}|${stageToMainPR.number}>`
);

await github.rest.repos.createDispatchEvent({
owner,
repo,
event_type: 'merge-to-stage',
});
}

console.log('Process successfully executed.');
} catch (error) {
console.error(error);
}
};

if (process.env.LOCAL_RUN) {
const { github, context } = getLocalConfigs();
main({
github,
context,
});
}

module.exports = main;
37 changes: 37 additions & 0 deletions .github/workflows/merge-to-main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Merge to main

on:
pull_request:
types: [labeled]
schedule:
- cron: '0 9 * * *' # Run every day at 9am UTC
workflow_dispatch: # Allow manual trigger

env:
MILO_RELEASE_SLACK_WH: ${{ secrets.MILO_RELEASE_SLACK_WH }}

jobs:
merge-to-main:
runs-on: ubuntu-latest
environment: milo_pr_merge
# Run this when manually triggered or on a schedule
# Otherwise run this only on PRs that are merged from stage to main
if: github.repository_owner == 'adobecom' && (github.event_name == 'workflow_dispatch' || github.event_name == 'schedule' || (github.event_name == 'pull_request' && github.event.pull_request.base.ref == 'main' && github.event.pull_request.head.ref == 'stage'))

steps:
- uses: actions/create-github-app-token@v1.10.0
id: milo-pr-merge-token
with:
app-id: ${{ secrets.MILO_PR_MERGE_APP_ID }}
private-key: ${{ secrets.MILO_PR_MERGE_PRIVATE_KEY }}

- name: Checkout repository
uses: actions/checkout@v4.1.4

- name: Merge to main
uses: actions/github-script@v7.0.1
with:
github-token: ${{ steps.milo-pr-merge-token.outputs.token }}
script: |
const main = require('./.github/workflows/merge-to-main.js')
main({ github, context })
52 changes: 2 additions & 50 deletions .github/workflows/merge-to-stage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const {

Check warning on line 1 in .github/workflows/merge-to-stage.js

View workflow job for this annotation

GitHub Actions / Running eslint

[eslint] reported by reviewdog 🐶 File ignored by default. Use a negated ignore pattern (like "--ignore-pattern '!<relative/path/to/filename>'") to override. Raw Output: {"fatal":false,"severity":1,"message":"File ignored by default. Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override."}
slackNotification,
getLocalConfigs,
isWithinRCP,
pulls: { addLabels, addFiles, getChecks, getReviews },
} = require('./helpers.js');

Expand Down Expand Up @@ -44,45 +45,6 @@ let body = `
- After: https://stage--milo--adobecom.hlx.live/?martech=off
`;

const RCPDates = [
{
start: new Date('2024-05-26T00:00:00-07:00'),
end: new Date('2024-06-01T00:00:00-07:00'),
},
{
start: new Date('2024-06-13T11:00:00-07:00'),
end: new Date('2024-06-13T14:00:00-07:00'),
},
{
start: new Date('2024-06-30T00:00:00-07:00'),
end: new Date('2024-07-06T00:00:00-07:00'),
},
{
start: new Date('2024-08-25T00:00:00-07:00'),
end: new Date('2024-08-31T00:00:00-07:00'),
},
{
start: new Date('2024-09-12T11:00:00-07:00'),
end: new Date('2024-09-12T14:00:00-07:00'),
},
{
start: new Date('2024-10-14T00:00:00-07:00'),
end: new Date('2024-11-18T17:00:00-08:00'),
},
{
start: new Date('2024-11-17T00:00:00-08:00'),
end: new Date('2024-11-30T00:00:00-08:00'),
},
{
start: new Date('2024-12-12T11:00:00-08:00'),
end: new Date('2024-12-12T14:00:00-08:00'),
},
{
start: new Date('2024-12-15T00:00:00-08:00'),
end: new Date('2025-01-02T00:00:00-08:00'),
},
];

const isHighPrio = (labels) => labels.includes(LABELS.highPriority);

const hasFailingChecks = (checks) =>
Expand Down Expand Up @@ -217,18 +179,8 @@ const main = async (params) => {
github = params.github;
owner = params.context.repo.owner;
repo = params.context.repo.repo;
if (isWithinRCP()) return console.log('Stopped, within RCP period.');

const now = new Date();
// We need to revisit this every year
if (now.getFullYear() !== 2024) {
throw new Error('ADD NEW RCPs');
}
for (const { start, end } of RCPDates) {
if (start <= now && now <= end) {
console.log('Current date is within a RCP. Stopping execution.');
return;
}
}
try {
const stageToMainPR = await getStageToMainPR();
console.log('has Stage to Main PR:', !!stageToMainPR);
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/merge-to-stage.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
schedule:
- cron: '0 */4 * * *' # Run every 4 hours
workflow_dispatch: # Allow manual trigger
repository_dispatch:
types: [merge-to-stage]

env:
MILO_RELEASE_SLACK_WH: ${{ secrets.MILO_RELEASE_SLACK_WH }}
Expand Down

0 comments on commit 0c0a36b

Please sign in to comment.