Skip to content

Commit

Permalink
Merge pull request #34262 from rayane-djouah/Create-Automation-for-wh…
Browse files Browse the repository at this point in the history
…en-main-fails

[No QA] Create Automation for when main fail
  • Loading branch information
pecanoro authored Feb 7, 2024
2 parents 79573fe + 534a3c7 commit c2dc5b6
Show file tree
Hide file tree
Showing 5 changed files with 187 additions and 0 deletions.
96 changes: 96 additions & 0 deletions .github/workflows/failureNotifier.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Notify on Workflow Failure

on:
workflow_run:
workflows: ["Process new code merged to main"]
types:
- completed

permissions:
issues: write

jobs:
notifyFailure:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'failure' }}
steps:
- name: Fetch Workflow Run Jobs
id: fetch-workflow-jobs
uses: actions/github-script@v7
with:
script: |
const runId = "${{ github.event.workflow_run.id }}";
const jobsData = await github.rest.actions.listJobsForWorkflowRun({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: runId,
});
return jobsData.data;
- name: Process Each Failed Job
uses: actions/github-script@v7
with:
script: |
const jobs = ${{ steps.fetch-workflow-jobs.outputs.result }};
const headCommit = "${{ github.event.workflow_run.head_commit.id }}";
const prData = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: headCommit,
});
const pr = prData.data[0];
const prLink = pr.html_url;
const prAuthor = pr.user.login;
const prMerger = "${{ github.event.workflow_run.actor.login }}";
const failureLabel = 'Workflow Failure';
for (let i = 0; i < jobs.total_count; i++) {
if (jobs.jobs[i].conclusion == 'failure') {
const jobName = jobs.jobs[i].name;
const jobLink = jobs.jobs[i].html_url;
const issues = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
labels: failureLabel,
state: 'open'
});
const existingIssue = issues.data.find(issue => issue.title.includes(jobName));
if (!existingIssue) {
const annotations = await github.rest.checks.listAnnotations({
owner: context.repo.owner,
repo: context.repo.repo,
check_run_id: jobs.jobs[i].id,
});
let errorMessage = "";
for(let j = 0; j < annotations.data.length; j++) {
errorMessage += annotations.data[j].annotation_level + ": ";
errorMessage += annotations.data[j].message + "\n";
}
const issueTitle = `Investigate workflow job failing on main: ${ jobName }`;
const issueBody = `🚨 **Failure Summary** 🚨:\n\n` +
`- **📋 Job Name**: [${ jobName }](${ jobLink })\n` +
`- **🔧 Failure in Workflow**: Process new code merged to main\n` +
`- **🔗 Triggered by PR**: [PR Link](${ prLink })\n` +
`- **👤 PR Author**: @${ prAuthor }\n` +
`- **🤝 Merged by**: @${ prMerger }\n` +
`- **🐛 Error Message**: \n ${errorMessage}\n\n` +
`⚠️ **Action Required** ⚠️:\n\n` +
`🛠️ A recent merge appears to have caused a failure in the job named [${ jobName }](${ jobLink }).\n` +
`This issue has been automatically created and labeled with \`${ failureLabel }\` for investigation. \n\n` +
`👀 **Please look into the following**:\n` +
`1. **Why the PR caused the job to fail?**\n` +
`2. **Address any underlying issues.**\n\n` +
`🐛 We appreciate your help in squashing this bug!`;
github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: issueTitle,
body: issueBody,
labels: [failureLabel, 'Daily'],
assignees: [prMerger, prAuthor]
});
}
}
}
1 change: 1 addition & 0 deletions .github/workflows/preDeploy.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Reminder: If this workflow's name changes, update the name in the dependent workflow at .github/workflows/failureNotifier.yml.
name: Process new code merged to main

on:
Expand Down
20 changes: 20 additions & 0 deletions workflow_tests/assertions/failureNotifierAssertions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
const utils = require('../utils/utils');

const assertNotifyFailureJobExecuted = (workflowResult, didExecute = true) => {
const steps = [
utils.createStepAssertion('Fetch Workflow Run Jobs', true, null, 'NOTIFYFAILURE', 'Fetch Workflow Run Jobs', [], []),
utils.createStepAssertion('Process Each Failed Job', true, null, 'NOTIFYFAILURE', 'Process Each Failed Job', [], []),
];

for (const expectedStep of steps) {
if (didExecute) {
expect(workflowResult).toEqual(expect.arrayContaining([expectedStep]));
} else {
expect(workflowResult).not.toEqual(expect.arrayContaining([expectedStep]));
}
}
};

module.exports = {
assertNotifyFailureJobExecuted,
};
59 changes: 59 additions & 0 deletions workflow_tests/failureNotifier.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const path = require('path');
const kieMockGithub = require('@kie/mock-github');
const assertions = require('./assertions/failureNotifierAssertions');
const mocks = require('./mocks/failureNotifierMocks');
const eAct = require('./utils/ExtendedAct');

jest.setTimeout(90 * 1000);
let mockGithub;
const FILES_TO_COPY_INTO_TEST_REPO = [
{
src: path.resolve(__dirname, '..', '.github', 'workflows', 'failureNotifier.yml'),
dest: '.github/workflows/failureNotifier.yml',
},
];

describe('test workflow failureNotifier', () => {
const actor = 'Dummy Actor';
beforeEach(async () => {
// create a local repository and copy required files
mockGithub = new kieMockGithub.MockGithub({
repo: {
testFailureNotifierWorkflowRepo: {
files: FILES_TO_COPY_INTO_TEST_REPO,

// if any branches besides main are need add: pushedBranches: ['staging', 'production'],
},
},
});

await mockGithub.setup();
});

afterEach(async () => {
await mockGithub.teardown();
});
it('runs the notify failure when main fails', async () => {
const repoPath = mockGithub.repo.getPath('testFailureNotifierWorkflowRepo') || '';
const workflowPath = path.join(repoPath, '.github', 'workflows', 'failureNotifier.yml');
let act = new eAct.ExtendedAct(repoPath, workflowPath);
const event = 'workflow_run';
act = act.setEvent({
workflow_run: {
name: 'Process new code merged to main',
conclusion: 'failure',
},
});
const testMockSteps = {
notifyFailure: mocks.FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS,
};
const result = await act.runEvent(event, {
workflowFile: path.join(repoPath, '.github', 'workflows', 'failureNotifier.yml'),
mockSteps: testMockSteps,
actor,
});

// assert execution with imported assertions
assertions.assertNotifyFailureJobExecuted(result);
});
});
11 changes: 11 additions & 0 deletions workflow_tests/mocks/failureNotifierMocks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable rulesdir/no-negated-variables */
const utils = require('../utils/utils');

// notifyfailure
const FAILURENOTIFIER__NOTIFYFAILURE__FETCH_WORKFLOW_RUN_JOBS__STEP_MOCK = utils.createMockStep('Fetch Workflow Run Jobs', 'Fetch Workflow Run Jobs', 'NOTIFYFAILURE', [], []);
const FAILURENOTIFIER__NOTIFYFAILURE__PROCESS_EACH_FAILED_JOB__STEP_MOCK = utils.createMockStep('Process Each Failed Job', 'Process Each Failed Job', 'NOTIFYFAILURE', [], []);
const FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS = [FAILURENOTIFIER__NOTIFYFAILURE__FETCH_WORKFLOW_RUN_JOBS__STEP_MOCK, FAILURENOTIFIER__NOTIFYFAILURE__PROCESS_EACH_FAILED_JOB__STEP_MOCK];

module.exports = {
FAILURENOTIFIER__NOTIFYFAILURE__STEP_MOCKS,
};

0 comments on commit c2dc5b6

Please sign in to comment.