Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Release] Stage to Main #2359

Merged
merged 8 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion .github/workflows/helpers.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,62 @@
// Those env variables are set by an github action automatically

Check warning on line 1 in .github/workflows/helpers.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."}
// For local testing, you should test on your fork.
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 @@

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 @@
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 {

Check warning on line 1 in .github/workflows/high-impact-alert.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,
} = 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 {

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 })
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 {

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 All @@ -14,7 +15,6 @@
highPriority: 'high priority',
readyForStage: 'Ready for Stage',
SOTPrefix: 'SOT',
highImpact: 'high-impact',
};
const TEAM_MENTIONS = [
'@adobecom/miq-sot',
Expand All @@ -24,8 +24,8 @@
'@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 @@
- 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 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 @@
});
}
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 @@
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
Loading