Skip to content

Commit 5facc71

Browse files
committed
WIP: Scale lambda
1 parent 64ff3ea commit 5facc71

File tree

5 files changed

+130
-8
lines changed

5 files changed

+130
-8
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Lambda Scale Runners
2+
on:
3+
push:
4+
branches:
5+
- master
6+
pull_request:
7+
paths:
8+
- .github/workflows/lambda-scale-runners.yml
9+
- "modules/runners/lambdas/scale-runners/**"
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-latest
14+
container: node:12
15+
defaults:
16+
run:
17+
working-directory: modules/runners/lambdas/scale-runners
18+
19+
steps:
20+
- uses: actions/checkout@v2
21+
- name: Install dependencies
22+
run: yarn install
23+
- name: Run tests
24+
run: yarn test
25+
- name: Build distribution
26+
run: yarn build

modules/runners/lambdas/scale-runners/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@
2323
"ts-node-dev": "^1.0.0-pre.44",
2424
"typescript": "^3.8.3"
2525
},
26-
"dependencies": {}
26+
"dependencies": {
27+
"@octokit/auth-app": "^2.4.5",
28+
"@octokit/rest": "^17.6.0"
29+
}
2730
}
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { handle } from './scale-runners/handler';
22

33
module.exports.handler = async (event: any, context: any, callback: any) => {
4-
const statusCode = await handle(event.headers, event.body);
5-
return callback(null, {
6-
statusCode: statusCode,
7-
});
4+
try {
5+
await handle(event.eventSource, JSON.parse(event.body));
6+
return callback(null);
7+
} catch (e) {
8+
console.error(e);
9+
return callback('Failed handling SQS event');
10+
}
811
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { ActionRequestMessage, handle } from './handler';
2+
3+
import { createAppAuth } from '@octokit/auth-app';
4+
import { Octokit } from '@octokit/rest';
5+
6+
jest.mock('@octokit/auth-app', () => ({
7+
createAppAuth: jest.fn().mockImplementation(() => jest.fn().mockImplementation(() => ({ token: 'Blaat' }))),
8+
}));
9+
const mockOctokit = { checks: { get: jest.fn() }, actions: { listRepoWorkflowRuns: jest.fn() } };
10+
jest.mock('@octokit/rest', () => ({
11+
Octokit: jest.fn().mockImplementation(() => mockOctokit),
12+
}));
13+
14+
const TEST_DATA: ActionRequestMessage = {
15+
id: 1,
16+
eventType: 'check_run',
17+
repositoryName: 'hello-world',
18+
repositoryOwner: 'Codertocat',
19+
installationId: 2,
20+
};
21+
22+
describe('handler', () => {
23+
beforeEach(() => {
24+
process.env.GITHUB_APP_KEY = 'TEST_CERTIFICATE_DATA';
25+
process.env.GITHUB_APP_ID = '1337';
26+
process.env.GITHUB_APP_CLIENT_ID = 'TEST_CLIENT_ID';
27+
process.env.GITHUB_APP_CLIENT_SECRET = 'TEST_CLIENT_SECRET';
28+
jest.clearAllMocks();
29+
mockOctokit.actions.listRepoWorkflowRuns.mockImplementation(() => ({
30+
total_count: 1,
31+
}));
32+
});
33+
34+
it('ignores non-sqs events', async () => {
35+
expect.assertions(1);
36+
expect(handle('aws:s3', TEST_DATA)).rejects.toEqual(Error('Cannot handle non-SQS events!'));
37+
});
38+
39+
it('checks queued workflows', async () => {
40+
await handle('aws:sqs', TEST_DATA);
41+
expect(mockOctokit.actions.listRepoWorkflowRuns).toBeCalledWith({
42+
owner: TEST_DATA.repositoryOwner,
43+
repo: TEST_DATA.repositoryName,
44+
status: 'queued',
45+
});
46+
});
47+
});
Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,48 @@
1-
import { IncomingHttpHeaders } from 'http';
1+
import { createAppAuth } from '@octokit/auth-app';
2+
import { Octokit } from '@octokit/rest';
23

3-
export const handle = async (headers: IncomingHttpHeaders, payload: any): Promise<number> => {
4-
return 200;
4+
export interface ActionRequestMessage {
5+
id: number;
6+
eventType: string;
7+
repositoryName: string;
8+
repositoryOwner: string;
9+
installationId: number;
10+
}
11+
12+
async function createGithubClient(installationId: number): Promise<Octokit> {
13+
const privateKey = process.env.GITHUB_APP_KEY as string;
14+
const appId: number = parseInt(process.env.GITHUB_APP_ID as string);
15+
const clientId = process.env.GITHUB_APP_CLIENT_ID as string;
16+
const clientSecret = process.env.GITHUB_APP_CLIENT_SECRET as string;
17+
18+
try {
19+
const auth = createAppAuth({
20+
id: appId,
21+
privateKey: privateKey,
22+
installationId: installationId,
23+
clientId: clientId,
24+
clientSecret: clientSecret,
25+
});
26+
const installationAuthentication = await auth({ type: 'installation' });
27+
28+
return new Octokit({
29+
auth: installationAuthentication.token,
30+
});
31+
} catch (e) {
32+
Promise.reject(e);
33+
}
34+
}
35+
36+
export const handle = async (eventSource: string, payload: ActionRequestMessage): Promise<void> => {
37+
if (eventSource !== 'aws:sqs') throw Error('Cannot handle non-SQS events!');
38+
const githubClient = await createGithubClient(payload.installationId);
39+
const queuedWorkflows = await githubClient.actions.listRepoWorkflowRuns({
40+
owner: payload.repositoryOwner,
41+
repo: payload.repositoryName,
42+
// @ts-ignore (typing is incorrect)
43+
status: 'queued',
44+
});
45+
console.info(
46+
`Repo ${payload.repositoryOwner}/${payload.repositoryName} has ${queuedWorkflows.total_count} queued workflow runs`,
47+
);
548
};

0 commit comments

Comments
 (0)