Skip to content

Commit

Permalink
[Release] Stage to Main (#2359)
Browse files Browse the repository at this point in the history
  • Loading branch information
milo-pr-merge[bot] authored Jun 3, 2024
2 parents b8b1871 + 0c0a36b commit be90c7d
Show file tree
Hide file tree
Showing 75 changed files with 2,688 additions and 1,073 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,
};
29 changes: 29 additions & 0 deletions .github/workflows/high-impact-alert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const {
slackNotification,
getLocalConfigs,
} = require('./helpers.js');

const main = async (params) => {
const { context } = params;

try {
if (context.payload.label.name !== 'high-impact') {
console.log('No high impact label detected');
return;
}

const { html_url, number, title } = context.payload.pull_request;
console.log('High impact label detected, sending Slack notification');
slackNotification(`:alert: High Impact PR has been opened: <${html_url}|#${number}: ${title}>.` +
` Please prioritize testing the proposed changes.`, process.env.SLACK_HIGH_IMPACT_PR_WEBHOOK);
} catch (error) {
console.error(error);
}
};

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

module.exports = main;
24 changes: 24 additions & 0 deletions .github/workflows/high-impact-alert.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: High Impact Alert

on:
pull_request:
types:
- labeled

env:
SLACK_HIGH_IMPACT_PR_WEBHOOK: ${{ secrets.SLACK_HIGH_IMPACT_PR_WEBHOOK }}

jobs:
send_alert:
if: github.repository_owner == 'adobecom'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4.1.4

- name: Send Slack message for high impact PRs
uses: actions/github-script@v7.0.1
with:
script: |
const main = require('./.github/workflows/high-impact-alert.js')
main({ github, context })
5 changes: 3 additions & 2 deletions .github/workflows/mark-stale-prs.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
name: "Close stale pull requests"
name: Close stale pull requests
on:
schedule:
- cron: "0 0 * * *"
- cron: '0 0 * * *'
workflow_dispatch:

jobs:
stale:
if: github.repository_owner == 'adobecom'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
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 {
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 })
73 changes: 6 additions & 67 deletions .github/workflows/merge-to-stage.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const {
slackNotification,
getLocalConfigs,
isWithinRCP,
pulls: { addLabels, addFiles, getChecks, getReviews },
} = require('./helpers.js');

Expand All @@ -14,7 +15,6 @@ const LABELS = {
highPriority: 'high priority',
readyForStage: 'Ready for Stage',
SOTPrefix: 'SOT',
highImpact: 'high-impact',
};
const TEAM_MENTIONS = [
'@adobecom/miq-sot',
Expand All @@ -24,8 +24,8 @@ const TEAM_MENTIONS = [
'@adobecom/document-cloud-sot',
];
const SLACK = {
merge: ({ html_url, number, title, highImpact }) =>
`:merged:${highImpact} PR merged to stage: <${html_url}|${number}: ${title}>.`,
merge: ({ html_url, number, title }) =>
`:merged: PR merged to stage: <${html_url}|${number}: ${title}>.`,
openedSyncPr: ({ html_url, number }) =>
`:fast_forward: Created <${html_url}|Stage to Main PR ${number}>`,
};
Expand All @@ -45,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 @@ -124,7 +85,8 @@ const getPRs = async () => {

const merge = async ({ prs }) => {
console.log(`Merging ${prs.length || 0} PRs that are ready... `);
for await (const { number, files, html_url, title, labels } of prs) {

for await (const { number, files, html_url, title } of prs) {
try {
if (files.some((file) => SEEN[file])) {
console.log(`Skipping ${number}: ${title} due to overlap in files.`);
Expand All @@ -140,24 +102,11 @@ const merge = async ({ prs }) => {
});
}
body = `- ${html_url}\n${body}`;
const isHighImpact = labels.includes(LABELS.highImpact);
if (isHighImpact && process.env.SLACK_HIGH_IMPACT_PR_WEBHOOK) {
await slackNotification(
SLACK.merge({
html_url,
number,
title,
highImpact: ' :alert: High impact',
}),
process.env.SLACK_HIGH_IMPACT_PR_WEBHOOK
);
}
await slackNotification(
SLACK.merge({
html_url,
number,
title,
highImpact: isHighImpact ? ' :alert: High impact' : '',
})
);
await new Promise((resolve) => setTimeout(resolve, 5000));
Expand Down Expand Up @@ -230,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
Loading

0 comments on commit be90c7d

Please sign in to comment.