Skip to content

Commit 9dcb671

Browse files
committed
Add experimatal code to terminate runners
1 parent c3c0bbb commit 9dcb671

File tree

4 files changed

+133
-5
lines changed

4 files changed

+133
-5
lines changed

modules/runners/lambdas/scale-runners/src/scale-runners/runners.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ export interface RunnerInputParameters {
5656
orgName?: string;
5757
}
5858

59+
export async function terminateRunner(runner: RunnerInfo): Promise<void> {
60+
const ec2 = new EC2();
61+
const result = await ec2
62+
.terminateInstances({
63+
InstanceIds: [runner.instanceId],
64+
})
65+
.promise();
66+
console.debug('Runner terminated.' + result.TerminatingInstances);
67+
}
68+
5969
export async function createRunner(runnerParameters: RunnerInputParameters): Promise<void> {
6070
const launchTemplateName = process.env.LAUNCH_TEMPLATE_NAME as string;
6171
const launchTemplateVersion = process.env.LAUNCH_TEMPLATE_VERSION as string;
Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,123 @@
1-
import { EC2 } from 'aws-sdk';
1+
import { createAppAuth } from '@octokit/auth-app';
2+
import { Octokit } from '@octokit/rest';
3+
import { AppAuth } from '@octokit/auth-app/dist-types/types';
4+
import { listRunners, terminateRunner, RunnerInfo } from './runners';
5+
import { createGithubAppAuth, createInstallationClient } from './scale-up';
6+
7+
// function createGithubAppAuth(installationId: number | undefined): AppAuth {
8+
// const privateKey = Buffer.from(process.env.GITHUB_APP_KEY_BASE64 as string, 'base64').toString();
9+
// const appId: number = parseInt(process.env.GITHUB_APP_ID as string);
10+
// const clientId = process.env.GITHUB_APP_CLIENT_ID as string;
11+
// const clientSecret = process.env.GITHUB_APP_CLIENT_SECRET as string;
12+
13+
// return createAppAuth({
14+
// id: appId,
15+
// privateKey: privateKey,
16+
// installationId: installationId,
17+
// clientId: clientId,
18+
// clientSecret: clientSecret,
19+
// });
20+
// }
21+
22+
// async function createInstallationClient(githubAppAuth: AppAuth): Promise<Octokit> {
23+
// const auth = await githubAppAuth({ type: 'installation' });
24+
// return new Octokit({ auth: auth.token });
25+
// }
26+
27+
// specific to scale down
28+
async function createAppClient(githubAppAuth: AppAuth): Promise<Octokit> {
29+
const auth = await githubAppAuth({ type: 'app' });
30+
return new Octokit({ auth: auth.token });
31+
}
32+
33+
interface Repo {
34+
isOrg: boolean;
35+
repoName: string;
36+
repoOwner: string;
37+
}
38+
39+
function getRepo(runner: RunnerInfo): Repo {
40+
if (runner.repo) {
41+
return {
42+
repoOwner: runner.repo?.split('/')[0] as string,
43+
repoName: runner.repo?.split('/')[1] as string,
44+
isOrg: false,
45+
};
46+
} else {
47+
return {
48+
repoOwner: runner.org as string,
49+
repoName: '',
50+
isOrg: true,
51+
};
52+
}
53+
}
54+
55+
async function createGitHubClientForRunner(runner: RunnerInfo): Promise<Octokit> {
56+
const githubClient = await createAppClient(createGithubAppAuth(undefined));
57+
const repo = getRepo(runner);
58+
59+
const repoInstallationId = repo.isOrg
60+
? (
61+
await githubClient.apps.getOrgInstallation({
62+
org: repo.repoOwner,
63+
})
64+
).data.id
65+
: (
66+
await githubClient.apps.getRepoInstallation({
67+
owner: repo.repoOwner,
68+
repo: repo.repoName,
69+
})
70+
).data.id;
71+
72+
return createInstallationClient(createGithubAppAuth(repoInstallationId));
73+
}
274

375
export async function scaleDown(): Promise<void> {
4-
console.info('Not implemented yet');
76+
const environment = process.env.ENVIRONMENT as string;
77+
const runners = await listRunners({
78+
environment: environment,
79+
});
80+
81+
if (runners?.length === 0) {
82+
console.debug(`No active runners found for environment: '${environment}'`);
83+
return;
84+
}
85+
86+
runners.forEach(async (r) => {
87+
const githubAppClient = await createGitHubClientForRunner(r);
88+
89+
const repo = getRepo(r);
90+
const registered = await githubAppClient.actions.listSelfHostedRunnersForRepo({
91+
owner: repo.repoOwner,
92+
repo: repo.repoName,
93+
});
94+
95+
console.log(registered.data.runners);
96+
registered.data.runners.forEach(async (a: any) => {
97+
const runnerName = a.name as string;
98+
if (runnerName === r.instanceId) {
99+
console.log(r.instanceId);
100+
try {
101+
const result = repo.isOrg
102+
? await githubAppClient.actions.deleteSelfHostedRunnerFromOrg({ runner_id: a.id, org: repo.repoOwner })
103+
: await githubAppClient.actions.deleteSelfHostedRunnerFromRepo({
104+
runner_id: a.id,
105+
owner: repo.repoOwner,
106+
repo: repo.repoName,
107+
});
108+
if (result?.status == 204) {
109+
terminateRunner(r);
110+
console.info(
111+
`AWS runner instance '${r.instanceId}' is terminated and GitHub runner '${runnerName}' is de-registered.`,
112+
);
113+
}
114+
console.info(
115+
`AWS runner instance '${r.instanceId}' is terminated and GitHub runner '${runnerName}' is de-registered.`,
116+
);
117+
} catch (e) {
118+
console.debug(`Runner '${runnerName}' cannot be de-registered, most likely the runner is active.`);
119+
}
120+
}
121+
});
122+
});
5123
}

modules/runners/lambdas/scale-runners/src/scale-runners/scale-up.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export interface ActionRequestMessage {
1212
installationId: number;
1313
}
1414

15-
function createGithubAppAuth(installationId: number): AppAuth {
15+
export function createGithubAppAuth(installationId: number | undefined): AppAuth {
1616
const privateKey = Buffer.from(process.env.GITHUB_APP_KEY_BASE64 as string, 'base64').toString();
1717
const appId: number = parseInt(process.env.GITHUB_APP_ID as string);
1818
const clientId = process.env.GITHUB_APP_CLIENT_ID as string;
@@ -27,7 +27,7 @@ function createGithubAppAuth(installationId: number): AppAuth {
2727
});
2828
}
2929

30-
async function createInstallationClient(githubAppAuth: AppAuth): Promise<Octokit> {
30+
export async function createInstallationClient(githubAppAuth: AppAuth): Promise<Octokit> {
3131
const auth = await githubAppAuth({ type: 'installation' });
3232
return new Octokit({ auth: auth.token });
3333
}

modules/runners/policies/lambda-scale-down.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"Action": [
77
"ec2:DescribeInstances*",
88
"ec2:DescribeTags",
9-
"ec2:CreateTags",
9+
"ec2:DeleteTags",
1010
"ec2:TerminateInstances"
1111
],
1212
"Resource": ["*"]

0 commit comments

Comments
 (0)