Skip to content

Commit

Permalink
feat: listen to issue_comment webhook to deploy front apps
Browse files Browse the repository at this point in the history
  • Loading branch information
nlepage committed Nov 5, 2024
1 parent 0b3965d commit 2dd0b37
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 1 deletion.
50 changes: 50 additions & 0 deletions build/controllers/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const repositoryToScalingoAppsReview = {
pix4pix: ['pix-4pix-front-review', 'pix-4pix-api-review'],
};

const repositoryToScalingoOnDemandAppsReview = {
pix: ['pix-front-review'],
};

const __dirname = url.fileURLToPath(new URL('.', import.meta.url));

function getMessageTemplate(repositoryName) {
Expand Down Expand Up @@ -139,6 +143,49 @@ async function _handleCloseRA(request, scalingoClient = ScalingoClient) {
return `Closed RA for PR ${prId} : ${result.join(', ')}.`;
}

async function _handleIssueComment(request, scalingoClient = ScalingoClient, githubService = commonGithubService) {
const payload = request.payload;
const repo = payload.repository.name;
const owner = payload.repository.owner.login;
const reviewApps = repositoryToScalingoOnDemandAppsReview[repo];
const pull_number = payload.issue.number;

if (!reviewApps) {
return `${repo} is not managed by Pix Bot nor on-demand review app.`;
}
let client;

try {
client = await scalingoClient.getInstance('reviewApps');
} catch (error) {
throw new Error(`Scalingo auth APIError: ${error.message}`);
}

const reviewAppName = `${reviewApps[0]}-pr${pull_number}`;

const reviewAppExists = await client.reviewAppExists(reviewAppName);
if (!reviewAppExists) {
return `Review app ${reviewAppName} does not exist.`;
}

const selectedApps = Array.from(payload.comment.body.matchAll(/^- \[[xX]\].+<!-- ([\w-]+) -->$/gm), ([, app]) => app);

if (selectedApps.length === 0) {
return `No front apps selected for review app ${reviewAppName}.`;
}

await client.bulkUpdateEnvVar(reviewAppName, {
CI_FRONT_TASKS: selectedApps.map((app) => `ci:${app}`).join(' '),
BUILD_FRONT_TASKS: selectedApps.map((app) => `build:${app}`).join(' '),
});

const branchName = await githubService.getPullRequestBranchName({ repo, owner, pull_number });

await client.deployUsingSCM(reviewAppName, branchName);

return 'ok';
}

async function deployPullRequest(
scalingoClient,
reviewApps,
Expand Down Expand Up @@ -238,6 +285,7 @@ async function processWebhook(
pushOnDefaultBranchWebhook = _pushOnDefaultBranchWebhook,
handleRA = _handleRA,
handleCloseRA = _handleCloseRA,
handleIssueComment = _handleIssueComment,
} = {},
) {
const eventName = request.headers['x-github-event'];
Expand All @@ -251,6 +299,8 @@ async function processWebhook(
return handleCloseRA(request);
}
return `Ignoring ${request.payload.action} action`;
} else if (eventName === 'issue_comment') {
return handleIssueComment(request);
} else {
return `Ignoring ${eventName} event`;
}
Expand Down
6 changes: 6 additions & 0 deletions common/services/github.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,12 @@ const github = {
},

commentPullRequest,

async getPullRequestBranchName({ owner, repo, pull_number }) {
const { pulls } = _createOctokit();
const pull = await pulls.get({ owner, repo, pull_number });
return pull.data.head.ref;
},
};

export default github;
7 changes: 7 additions & 0 deletions common/services/scalingo-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,13 @@ class ScalingoClient {
logger.error(err);
}
}

async bulkUpdateEnvVar(appName, variables) {
await this.client.Environment.bulkUpdate(
appName,
Object.entries(variables).map(([name, value]) => ({ name, value })),
);
}
}

async function _isUrlReachable(url) {
Expand Down
93 changes: 92 additions & 1 deletion test/acceptance/build/github_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,19 @@ describe('Acceptance | Build | Github', function () {
}

function deleteReviewAppNock({ reviewAppName, returnCode = StatusCodes.NO_CONTENT }) {
nock('https://scalingo.reviewApps')
return nock('https://scalingo.reviewApps')
.delete(`/v1/apps/${reviewAppName}?current_name=${reviewAppName}`)
.reply(returnCode);
}

function bulkUpdateEnvVarNock({ reviewAppName, variables = {}, returnCode = StatusCodes.OK }) {
return nock('https://scalingo.reviewApps')
.put(`/v1/apps/${reviewAppName}/variables`, {
variables: Object.entries(variables).map(([name, value]) => ({ name, value })),
})
.reply(returnCode);
}

let body;

['opened', 'reopened'].forEach((action) => {
Expand Down Expand Up @@ -726,6 +734,89 @@ describe('Acceptance | Build | Github', function () {
});
});

describe('on issue_comment event', function () {
describe('when user has checked some apps', function () {
it('should deploy the corresponding review apps', async function () {
const scalingoAuth = nock('https://auth.scalingo.com').post('/v1/tokens/exchange').reply(StatusCodes.OK);

const scalingoRAExists = getAppNock({ reviewAppName: 'pix-front-review-pr2' });

const scalingoBulkUpdateEnvVar = bulkUpdateEnvVarNock({
reviewAppName: 'pix-front-review-pr2',
variables: {
CI_FRONT_TASKS: 'ci:mon-pix ci:certif',
BUILD_FRONT_TASKS: 'build:mon-pix build:certif',
},
});

const githubPull = nock('https://api.github.com')
.get('/repos/1024pix/pix/pulls/2')
.reply(200, {
number: 2,
head: {
ref: 'pix-12345-graphql-api',
},
});

const scalingoManualDeploy = getManualDeployNock({
reviewAppName: 'pix-front-review-pr2',
branch: 'pix-12345-graphql-api',
});

body = {
action: 'edited',
comment: {
body: `Une fois les applications déployées, elles seront accessibles via les liens suivants :
- [API](https://api-pr2.review.pix.fr/api/)
- [Audit Logger](https://pix-audit-logger-review-pr2.osc-fr1.scalingo.io/api/)
- [x] [App (.fr)](https://app-pr2.review.pix.fr) / [App (.org)](https://app-pr2.review.pix.org) <!-- mon-pix -->
- [ ] [Orga (.fr)](https://orga-pr2.review.pix.fr) / [Orga (.org)](https://orga-pr2.review.pix.org) <!-- orga -->
- [x] [Certif (.fr)](https://certif-pr2.review.pix.fr) / [Certif (.org)](https://certif-pr2.review.pix.org) <!-- certif -->
- [ ] [Junior](https://junior-pr2.review.pix.fr) <!-- junior -->
- [ ] [Admin](https://admin-pr2.review.pix.fr) <!-- admin -->
Les variables d'environnement seront accessibles via les liens suivants :
* [scalingo front](https://dashboard.scalingo.com/apps/osc-fr1/pix-front-review-pr2/environment)
* [scalingo api](https://dashboard.scalingo.com/apps/osc-fr1/pix-api-review-pr2/environment)
* [scalingo audit-logger](https://dashboard.scalingo.com/apps/osc-fr1/pix-audit-logger-review-pr2/environment)
`,
user: {
login: 'pix-bot-github',
},
},
issue: {
number: 2,
},
repository: {
name: 'pix',
full_name: '1024pix/pix',
owner: {
login: '1024pix',
},
fork: false,
organization: '1024pix',
},
};

const res = await server.inject({
method: 'POST',
url: '/github/webhook',
headers: {
...createGithubWebhookSignatureHeader(JSON.stringify(body)),
'x-github-event': 'issue_comment',
},
payload: body,
});
expect(res.statusCode).to.equal(StatusCodes.OK);
expect(scalingoRAExists.isDone()).to.be.true;
expect(scalingoAuth.isDone()).to.be.true;
expect(scalingoBulkUpdateEnvVar.isDone()).to.be.true;
expect(githubPull.isDone()).to.be.true;
expect(scalingoManualDeploy.isDone()).to.be.true;
});
});
});

it('responds with 200 and do nothing for other event', async function () {
body = {};
const res = await server.inject({
Expand Down
23 changes: 23 additions & 0 deletions test/unit/build/services/github_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -603,4 +603,27 @@ describe('Unit | Build | github-test', function () {
});
});
});

describe('#getPullRequestBranchName', function () {
it('should retrieve branch name for pull request id', async function () {
// given
nock('https://api.github.com')
.get('/repos/toto/lasticot/pulls/666')
.reply(200, {
number: 666,
head: {
ref: 'pix-12345-feature-bug',
},
});
const owner = 'toto';
const repo = 'lasticot';
const pull_number = 666;

// when
const branchName = await githubService.getPullRequestBranchName({ owner, repo, pull_number });

// then
expect(branchName).to.equal('pix-12345-feature-bug');
});
});
});
32 changes: 32 additions & 0 deletions test/unit/common/services/scalingo-client_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -848,4 +848,36 @@ describe('Scalingo client', function () {
expect(clientAppsDestroy).to.have.been.calledWithExactly(appName, appName);
});
});

describe('#Scalingo.bulkUpdateEnvVar', function () {
let scalingoClient;
let bulkUpdateStub;

beforeEach(async function () {
bulkUpdateStub = sinon.stub();
const clientStub = {
clientFromToken: async function () {
return {
Environment: { bulkUpdate: bulkUpdateStub },
};
},
};
scalingoClient = await ScalingoClient.getInstance('production', clientStub);
});

it('should update several environment variables', async function () {
const appId = 'pix-front-review-pr2';
const variables = {
FOO: 'foo',
BAR: 'bar',
};

await scalingoClient.bulkUpdateEnvVar(appId, variables);

expect(bulkUpdateStub).to.have.been.calledWithExactly(appId, [
{ name: 'FOO', value: 'foo' },
{ name: 'BAR', value: 'bar' },
]);
});
});
});

0 comments on commit 2dd0b37

Please sign in to comment.