Skip to content

Commit

Permalink
WIP commit
Browse files Browse the repository at this point in the history
  • Loading branch information
gertjanmaas committed May 7, 2020
1 parent 67f80ad commit a1e0479
Show file tree
Hide file tree
Showing 15 changed files with 509 additions and 49 deletions.
8 changes: 7 additions & 1 deletion examples/default/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,14 @@ module "runners" {
Project = "ProjectX"
}

github_app_webhook_secret = random_password.random.result
github_app_webhook_secret = var.github_app_webhook_secret

github_app_client_id = var.github_app_client_id
github_app_client_secret = var.github_app_client_secret
github_app_id = var.github_app_id
github_app_key_base64 = var.github_app_key_base64

enable_organization_runners = var.enable_organization_runners
}


13 changes: 13 additions & 0 deletions examples/default/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
variable "enable_organization_runners" {
type = bool
}

variable "github_app_key_base64" {}

variable "github_app_id" {}

variable "github_app_client_id" {}

variable "github_app_client_secret" {}

variable "github_app_webhook_secret" {}
13 changes: 5 additions & 8 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,15 @@ module "runners" {

s3_location_runner_distribution = module.dsitrubtion_cache.s3_location_runner_distribution
sqs = module.agent.sqs
}

module "agent" {
source = "./modules/agent"
github_app_client_id = var.github_app_client_id
github_app_client_secret = var.github_app_client_secret
github_app_id = var.github_app_id
github_app_key_base64 = var.github_app_key_base64

aws_region = var.aws_region
environment = var.environment
tags = var.tags
github_app_webhook_secret = "blaat"
enable_organization_runners = var.enable_organization_runners
}


module "agent" {
source = "./modules/agent"

Expand Down
2 changes: 1 addition & 1 deletion modules/agent/webhook_queue.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
resource "aws_sqs_queue" "webhook_events" {
name = "${var.environment}-webhook-events.fifo"
delay_seconds = 30
delay_seconds = 0
fifo_queue = true
receive_wait_time_seconds = 10
content_based_deduplication = true
Expand Down
6 changes: 4 additions & 2 deletions modules/runners/lambdas/scale-runners/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
"dist": "yarn build && cd dist && zip ../scale-runners.zip index.js"
},
"devDependencies": {
"@types/aws-lambda": "^8.10.51",
"@types/express": "^4.17.3",
"@types/jest": "^25.2.1",
"@types/node": "^13.13.4",
"@zeit/ncc": "^0.22.1",
"aws-sdk": "^2.645.0",
"aws-sdk": "^2.671.0",
"body-parser": "^1.19.0",
"express": "^4.17.1",
"jest": "^25.4.0",
Expand All @@ -25,6 +26,7 @@
},
"dependencies": {
"@octokit/auth-app": "^2.4.5",
"@octokit/rest": "^17.6.0"
"@octokit/rest": "^17.6.0",
"yn": "^4.0.0"
}
}
8 changes: 6 additions & 2 deletions modules/runners/lambdas/scale-runners/src/lambda.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { handle } from './scale-runners/handler';
import { SQSEvent } from 'aws-lambda';

module.exports.handler = async (event: any, context: any, callback: any) => {
module.exports.handler = async (event: SQSEvent, context: any, callback: any) => {
console.log(event);
try {
await handle(event.eventSource, JSON.parse(event.body));
for (const e of event.Records) {
await handle(e.eventSource, JSON.parse(e.body));
}
return callback(null);
} catch (e) {
console.error(e);
Expand Down
6 changes: 3 additions & 3 deletions modules/runners/lambdas/scale-runners/src/local.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import express from 'express';
import bodyParser from 'body-parser';
import { handle } from './scale-runners/handler';
import { handle, ActionRequestMessage } from './scale-runners/handler';

const app = express();

app.use(bodyParser.json());

app.post('/event_handler', (req, res) => {
handle(req.headers, JSON.stringify(req.body))
.then((c) => res.status(c).end())
handle('aws:sqs', JSON.parse(req.body) as ActionRequestMessage)
.then()
.catch((e) => {
console.log(e);
res.status(404);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ import { Octokit } from '@octokit/rest';
jest.mock('@octokit/auth-app', () => ({
createAppAuth: jest.fn().mockImplementation(() => jest.fn().mockImplementation(() => ({ token: 'Blaat' }))),
}));
const mockOctokit = { checks: { get: jest.fn() }, actions: { listRepoWorkflowRuns: jest.fn() } };
const mockOctokit = {
checks: { get: jest.fn() },
actions: {
listRepoWorkflowRuns: jest.fn(),
listSelfHostedRunnersForOrg: jest.fn(),
listSelfHostedRunnersForRepo: jest.fn(),
},
};
jest.mock('@octokit/rest', () => ({
Octokit: jest.fn().mockImplementation(() => mockOctokit),
}));
Expand All @@ -21,14 +28,31 @@ const TEST_DATA: ActionRequestMessage = {

describe('handler', () => {
beforeEach(() => {
process.env.GITHUB_APP_KEY = 'TEST_CERTIFICATE_DATA';
process.env.GITHUB_APP_KEY_BASE64 = 'TEST_CERTIFICATE_DATA';
process.env.GITHUB_APP_ID = '1337';
process.env.GITHUB_APP_CLIENT_ID = 'TEST_CLIENT_ID';
process.env.GITHUB_APP_CLIENT_SECRET = 'TEST_CLIENT_SECRET';
jest.clearAllMocks();
mockOctokit.actions.listRepoWorkflowRuns.mockImplementation(() => ({
total_count: 1,
data: {
total_count: 1,
},
}));
const mockRunnersReturnValue = {
data: {
total_count: 1,
runners: [
{
id: 23,
name: 'Test Runner',
status: 'online',
os: 'linux',
},
],
},
};
mockOctokit.actions.listSelfHostedRunnersForOrg.mockImplementation(() => mockRunnersReturnValue);
mockOctokit.actions.listSelfHostedRunnersForRepo.mockImplementation(() => mockRunnersReturnValue);
});

it('ignores non-sqs events', async () => {
Expand All @@ -44,4 +68,31 @@ describe('handler', () => {
status: 'queued',
});
});

// describe('on org level', () => {
// beforeAll(() => {
// process.env.ENABLE_ORGANIZATION_RUNNERS = 'true';
// });

// it('gets the current org level runners', async () => {
// await handle('aws:sqs', TEST_DATA);
// expect(mockOctokit.actions.listSelfHostedRunnersForOrg).toBeCalledWith({
// org: TEST_DATA.repositoryOwner,
// });
// });
// });

// describe('on repo level', () => {
// beforeAll(() => {
// delete process.env.ENABLE_ORGANIZATION_RUNNERS;
// });

// it('gets the current repo level runners', async () => {
// await handle('aws:sqs', TEST_DATA);
// expect(mockOctokit.actions.listSelfHostedRunnersForRepo).toBeCalledWith({
// owner: TEST_DATA.repositoryOwner,
// repo: TEST_DATA.repositoryName,
// });
// });
// });
});
54 changes: 34 additions & 20 deletions modules/runners/lambdas/scale-runners/src/scale-runners/handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { createAppAuth } from '@octokit/auth-app';
import { Octokit } from '@octokit/rest';
import { AppAuth } from '@octokit/auth-app/dist-types/types';
import yn from 'yn';

export interface ActionRequestMessage {
id: number;
Expand All @@ -9,40 +11,52 @@ export interface ActionRequestMessage {
installationId: number;
}

async function createGithubClient(installationId: number): Promise<Octokit> {
const privateKey = process.env.GITHUB_APP_KEY as string;
function createGithubAppAuth(installationId: number): AppAuth {
const privateKey = Buffer.from(process.env.GITHUB_APP_KEY_BASE64 as string, 'base64').toString();
const appId: number = parseInt(process.env.GITHUB_APP_ID as string);
const clientId = process.env.GITHUB_APP_CLIENT_ID as string;
const clientSecret = process.env.GITHUB_APP_CLIENT_SECRET as string;

try {
const auth = createAppAuth({
id: appId,
privateKey: privateKey,
installationId: installationId,
clientId: clientId,
clientSecret: clientSecret,
});
const installationAuthentication = await auth({ type: 'installation' });
return createAppAuth({
id: appId,
privateKey: privateKey,
installationId: installationId,
clientId: clientId,
clientSecret: clientSecret,
});
}

return new Octokit({
auth: installationAuthentication.token,
});
} catch (e) {
Promise.reject(e);
}
async function createInstallationClient(githubAppAuth: AppAuth): Promise<Octokit> {
const auth = await githubAppAuth({ type: 'installation' });
return new Octokit({ auth: auth.token });
}

export const handle = async (eventSource: string, payload: ActionRequestMessage): Promise<void> => {
if (eventSource !== 'aws:sqs') throw Error('Cannot handle non-SQS events!');
const githubClient = await createGithubClient(payload.installationId);
const queuedWorkflows = await githubClient.actions.listRepoWorkflowRuns({
const enableOrgLevel = yn(process.env.ENABLE_ORGANIZATION_RUNNERS);
const githubAppAuth = createGithubAppAuth(payload.installationId);
const githubInstallationClient = await createInstallationClient(githubAppAuth);
const queuedWorkflows = await githubInstallationClient.actions.listRepoWorkflowRuns({
owner: payload.repositoryOwner,
repo: payload.repositoryName,
// @ts-ignore (typing is incorrect)
status: 'queued',
});
console.info(
`Repo ${payload.repositoryOwner}/${payload.repositoryName} has ${queuedWorkflows.total_count} queued workflow runs`,
`Repo ${payload.repositoryOwner}/${payload.repositoryName} has ${queuedWorkflows.data.total_count} queued workflow runs`,
);

if (queuedWorkflows.data.total_count > 0) {
// console.log(enableOrgLevel);
// const currentRunners = enableOrgLevel
// ? await githubInstallationClient.actions.listSelfHostedRunnersForOrg({
// org: payload.repositoryOwner,
// })
// : await githubInstallationClient.actions.listSelfHostedRunnersForRepo({
// owner: payload.repositoryOwner,
// repo: payload.repositoryName,
// });
// // const currentOnlineRunners = currentRunners.data.runners.filter((r) => r.status === 'online');
// // if (currentOnlineRunners.length > 0)
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { listRunners } from './runners';
import { handle } from './handler';
import { EC2 } from 'aws-sdk';

jest.mock('./handler');
const mockEC2 = { describeInstances: jest.fn() };
jest.mock('aws-sdk', () => ({
EC2: jest.fn().mockImplementation(() => mockEC2),
}));

describe('list instances', () => {
beforeAll(() => {
jest.clearAllMocks();
});
it('returns a list of instances', () => {
listRunners();
});
});
27 changes: 27 additions & 0 deletions modules/runners/lambdas/scale-runners/src/scale-runners/runners.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { EC2 } from 'aws-sdk';

export interface RunnerInfo {
instanceId: string;
launchTime: Date;
repo: string;
org: string;
}

const ec2 = new EC2();
export async function listRunners(
repoName: string | undefined = undefined,
orgName: string | undefined = undefined,
): Promise<RunnerInfo[]> {
let filters = [
{ Name: 'tag:Application', Values: ['github-action-runner'] },
{ Name: 'instance-state-name', Values: ['running', 'pending'] },
];
if (repoName !== undefined) {
filters.push({ Name: 'tag:Repo', Values: [repoName] });
}
if (orgName !== undefined) {
filters.push({ Name: 'tag:Org', Values: [orgName] });
}
const runningInstances = await ec2.describeInstances({ Filters: filters }).promise();
return [{ instanceId: 'i-123', launchTime: new Date(), repo: 'bla', org: 'bla' }];
}
Loading

0 comments on commit a1e0479

Please sign in to comment.