Skip to content

Commit 8a5d1d0

Browse files
committed
feat: introduce github caching
1 parent ceb1182 commit 8a5d1d0

File tree

6 files changed

+78
-13
lines changed

6 files changed

+78
-13
lines changed

lambdas/functions/control-plane/src/github/auth.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { createAppAuth } from '@octokit/auth-app';
2-
import { StrategyOptions } from '@octokit/auth-app/dist-types/types';
1+
import { createAppAuth, type StrategyOptions } from '@octokit/auth-app';
32
import { request } from '@octokit/request';
43
import { RequestInterface, RequestParameters } from '@octokit/types';
54
import { getParameter } from '@aws-github-runner/aws-ssm-util';
65
import * as nock from 'nock';
7-
6+
import { reset } from './cache';
87
import { createGithubAppAuth, createOctokitClient } from './auth';
98
import { describe, it, expect, beforeEach, vi } from 'vitest';
109

@@ -29,6 +28,7 @@ const PARAMETER_GITHUB_APP_KEY_BASE64_NAME = `/actions-runner/${ENVIRONMENT}/git
2928
const mockedGet = vi.mocked(getParameter);
3029

3130
beforeEach(() => {
31+
reset(); // clear all caches before each test
3232
vi.resetModules();
3333
vi.clearAllMocks();
3434
process.env = { ...cleanEnv };

lambdas/functions/control-plane/src/github/auth.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import { throttling } from '@octokit/plugin-throttling';
2222
import { createChildLogger } from '@aws-github-runner/aws-powertools-util';
2323
import { getParameter } from '@aws-github-runner/aws-ssm-util';
2424
import { EndpointDefaults } from '@octokit/types';
25+
import { getInstallationAuthObject, getAuthConfig, createAuthCacheKey, createAuthConfigCacheKey } from './cache';
26+
import type { GithubAppConfig } from './types';
2527

2628
const logger = createChildLogger('gh-auth');
2729

@@ -64,32 +66,47 @@ export async function createGithubInstallationAuth(
6466
installationId: number | undefined,
6567
ghesApiUrl = '',
6668
): Promise<InstallationAccessTokenAuthentication> {
67-
const auth = await createAuth(installationId, ghesApiUrl);
68-
const installationAuthOptions: InstallationAuthOptions = { type: 'installation', installationId };
69-
return auth(installationAuthOptions);
69+
const cacheKey = createAuthCacheKey('installation', installationId, ghesApiUrl);
70+
71+
return getInstallationAuthObject(cacheKey, async () => {
72+
const auth = await createAuth(installationId, ghesApiUrl);
73+
const installationAuthOptions: InstallationAuthOptions = { type: 'installation', installationId };
74+
return auth(installationAuthOptions);
75+
});
7076
}
7177

7278
async function createAuth(installationId: number | undefined, ghesApiUrl: string): Promise<AuthInterface> {
73-
const appId = parseInt(await getParameter(process.env.PARAMETER_GITHUB_APP_ID_NAME));
74-
let authOptions: StrategyOptions = {
75-
appId,
76-
privateKey: Buffer.from(
79+
const configCacheKey = createAuthConfigCacheKey(ghesApiUrl);
80+
81+
const config = await getAuthConfig(configCacheKey, async (): Promise<GithubAppConfig> => {
82+
const appId = parseInt(await getParameter(process.env.PARAMETER_GITHUB_APP_ID_NAME));
83+
const privateKey = Buffer.from(
7784
await getParameter(process.env.PARAMETER_GITHUB_APP_KEY_BASE64_NAME),
7885
'base64',
7986
// replace literal \n characters with new lines to allow the key to be stored as a
8087
// single line variable. This logic should match how the GitHub Terraform provider
8188
// processes private keys to retain compatibility between the projects
8289
)
8390
.toString()
84-
.replace('/[\\n]/g', String.fromCharCode(10)),
91+
.replace('/[\\n]/g', String.fromCharCode(10));
92+
93+
return {
94+
appId,
95+
privateKey,
96+
};
97+
});
98+
99+
let authOptions: StrategyOptions = {
100+
appId: config.appId,
101+
privateKey: config.privateKey,
85102
};
86103
if (installationId) authOptions = { ...authOptions, installationId };
87104

88105
logger.debug(`GHES API URL: ${ghesApiUrl}`);
89106
if (ghesApiUrl) {
90107
authOptions.request = request.defaults({
91108
baseUrl: ghesApiUrl,
92-
});
109+
}) as RequestInterface;
93110
}
94111
return createAppAuth(authOptions);
95112
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { InstallationAccessTokenAuthentication } from '@octokit/auth-app';
2+
import type { GithubAppConfig } from './types';
3+
4+
const installationAuthObjects = new Map<string, InstallationAccessTokenAuthentication>();
5+
const authConfigs = new Map<string, GithubAppConfig>();
6+
7+
export function createAuthCacheKey(type: 'app' | 'installation', installationId?: number, ghesApiUrl: string = '') {
8+
const id = installationId || 'none';
9+
return `${type}-auth-${id}-${ghesApiUrl}`;
10+
}
11+
12+
export function createAuthConfigCacheKey(ghesApiUrl: string = '') {
13+
return `auth-config-${ghesApiUrl}`;
14+
}
15+
16+
export async function getInstallationAuthObject(
17+
key: string,
18+
creator: () => Promise<InstallationAccessTokenAuthentication>,
19+
): Promise<InstallationAccessTokenAuthentication> {
20+
if (installationAuthObjects.has(key)) {
21+
return installationAuthObjects.get(key)!;
22+
}
23+
24+
const authObj = await creator();
25+
installationAuthObjects.set(key, authObj);
26+
return authObj;
27+
}
28+
29+
export async function getAuthConfig(key: string, creator: () => Promise<GithubAppConfig>) {
30+
if (authConfigs.has(key)) {
31+
return authConfigs.get(key)!;
32+
}
33+
34+
const config = await creator();
35+
authConfigs.set(key, config);
36+
return config;
37+
}
38+
39+
export function reset() {
40+
installationAuthObjects.clear();
41+
authConfigs.clear();
42+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './cache';
2+
export * from './types';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export interface GithubAppConfig {
2+
appId: number;
3+
privateKey: string;
4+
installationId?: number;
5+
}

lambdas/functions/control-plane/src/local.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const sqsEvent = {
77
{
88
messageId: 'e8d74d08-644e-42ca-bf82-a67daa6c4dad',
99
receiptHandle:
10-
// eslint-disable-next-line max-len
1110
'AQEBCpLYzDEKq4aKSJyFQCkJduSKZef8SJVOperbYyNhXqqnpFG5k74WygVAJ4O0+9nybRyeOFThvITOaS21/jeHiI5fgaM9YKuI0oGYeWCIzPQsluW5CMDmtvqv1aA8sXQ5n2x0L9MJkzgdIHTC3YWBFLQ2AxSveOyIHwW+cHLIFCAcZlOaaf0YtaLfGHGkAC4IfycmaijV8NSlzYgDuxrC9sIsWJ0bSvk5iT4ru/R4+0cjm7qZtGlc04k9xk5Fu6A+wRxMaIyiFRY+Ya19ykcevQldidmEjEWvN6CRToLgclk=',
1211
body: {
1312
repositoryName: 'self-hosted',

0 commit comments

Comments
 (0)