Skip to content

Commit d309e3d

Browse files
committed
feat: Adding support eventbridge
1 parent 4708125 commit d309e3d

35 files changed

+1796
-294
lines changed

examples/default/main.tf

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,13 @@ module "runners" {
9898
runner_name_prefix = "${local.environment}_"
9999

100100
# Enable debug logging for the lambda functions
101-
log_level = "info"
101+
log_level = "debug"
102+
103+
tracing_config = {
104+
mode = "Active"
105+
capture_error = true
106+
capture_http_requests = true
107+
}
102108

103109
enable_ami_housekeeper = true
104110
ami_housekeeper_cleanup_config = {
@@ -158,3 +164,7 @@ module "webhook_github_app" {
158164
# name = "alias/github/action-runners"
159165
# target_key_id = aws_kms_key.github.key_id
160166
# }
167+
# moved {
168+
# from = module.runners.module.webhook.aws_lambda_function.webhook
169+
# to = module.runners.module.webhook.module.webhook.aws_lambda_function.webhook
170+
# }

lambdas/functions/ami-housekeeper/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
"@aws-github-runner/aws-ssm-util": "*",
4242
"@aws-sdk/client-ec2": "^3.670.0",
4343
"@aws-sdk/client-ssm": "^3.670.0",
44-
"@aws-sdk/types": "^3.664.0",
44+
"@aws-sdk/types": "^3.667.0",
4545
"cron-parser": "^4.9.0",
4646
"typescript": "^5.5.4"
4747
},

lambdas/functions/control-plane/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"@aws-lambda-powertools/parameters": "^2.9.0",
4444
"@aws-sdk/client-ec2": "^3.670.0",
4545
"@aws-sdk/client-sqs": "^3.670.0",
46-
"@aws-sdk/types": "^3.664.0",
46+
"@aws-sdk/types": "^3.667.0",
4747
"@middy/core": "^4.7.0",
4848
"@octokit/auth-app": "6.1.2",
4949
"@octokit/core": "5.2.0",

lambdas/functions/gh-agent-syncer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
"@aws-github-runner/aws-powertools-util": "*",
4040
"@aws-sdk/client-s3": "^3.673.0",
4141
"@aws-sdk/lib-storage": "^3.673.0",
42-
"@aws-sdk/types": "^3.664.0",
42+
"@aws-sdk/types": "^3.667.0",
4343
"@middy/core": "^4.7.0",
4444
"@octokit/rest": "20.1.1",
4545
"axios": "^1.7.7"

lambdas/functions/termination-watcher/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"dependencies": {
3838
"@aws-github-runner/aws-powertools-util": "*",
3939
"@aws-sdk/client-ec2": "^3.670.0",
40-
"@aws-sdk/types": "^3.664.0",
40+
"@aws-sdk/types": "^3.667.0",
4141
"@middy/core": "^4.7.0",
4242
"typescript": "^5.5.4"
4343
},

lambdas/functions/webhook/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"all": "yarn build && yarn format && yarn lint && yarn test"
1717
},
1818
"devDependencies": {
19+
"@aws-sdk/client-eventbridge": "^3.670.0",
1920
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
2021
"@types/aws-lambda": "^8.10.145",
2122
"@types/express": "^4.17.21",
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
import { getParameter } from '@aws-github-runner/aws-ssm-util';
2+
import { ConfigWebhook, ConfigWebhookEventBridge, ConfigDispatcher } from './ConfigLoader';
3+
import { mocked } from 'jest-mock';
4+
import { logger } from '@aws-github-runner/aws-powertools-util';
5+
6+
jest.mock('@aws-github-runner/aws-ssm-util');
7+
8+
describe('ConfigLoader Tests', () => {
9+
beforeEach(() => {
10+
jest.clearAllMocks();
11+
ConfigWebhook.reset();
12+
ConfigWebhookEventBridge.reset();
13+
ConfigDispatcher.reset();
14+
logger.setLogLevel('DEBUG');
15+
16+
// clear process.env
17+
for (const key of Object.keys(process.env)) {
18+
delete process.env[key];
19+
}
20+
});
21+
22+
describe('Check base object', () => {
23+
function setupConfiguration(): void {
24+
process.env.EVENT_BUS_NAME = 'event-bus';
25+
process.env.PARAMETER_RUNNER_MATCHER_CONFIG_PATH = '/path/to/matcher/config';
26+
process.env.PARAMETER_GITHUB_APP_WEBHOOK_SECRET = '/path/to/webhook/secret';
27+
const matcherConfig = [
28+
{
29+
id: '1',
30+
arn: 'arn:aws:sqs:us-east-1:123456789012:queue1',
31+
fifo: false,
32+
matcherConfig: {
33+
labelMatchers: [['label1', 'label2']],
34+
exactMatch: true,
35+
},
36+
},
37+
];
38+
mocked(getParameter).mockImplementation(async (paramPath: string) => {
39+
if (paramPath === '/path/to/matcher/config') {
40+
return JSON.stringify(matcherConfig);
41+
}
42+
if (paramPath === '/path/to/webhook/secret') {
43+
return 'secret';
44+
}
45+
return '';
46+
});
47+
}
48+
49+
it('should return the same instance of ConfigWebhook (singleton)', async () => {
50+
setupConfiguration();
51+
const config1 = await ConfigWebhook.load();
52+
const config2 = await ConfigWebhook.load();
53+
54+
expect(config1).toBe(config2);
55+
expect(getParameter).toHaveBeenCalledTimes(2);
56+
});
57+
58+
it('should return the same instance of ConfigWebhookEventBridge (singleton)', async () => {
59+
setupConfiguration();
60+
const config1 = await ConfigWebhookEventBridge.load();
61+
const config2 = await ConfigWebhookEventBridge.load();
62+
63+
expect(config1).toBe(config2);
64+
expect(getParameter).toHaveBeenCalledTimes(1);
65+
});
66+
67+
it('should return the same instance of ConfigDispatcher (singleton)', async () => {
68+
setupConfiguration();
69+
const config1 = await ConfigDispatcher.load();
70+
const config2 = await ConfigDispatcher.load();
71+
72+
expect(config1).toBe(config2);
73+
expect(getParameter).toHaveBeenCalledTimes(1);
74+
});
75+
76+
it('should filter secrets from being logged', async () => {
77+
setupConfiguration();
78+
const spy = jest.spyOn(logger, 'debug');
79+
80+
const config = await ConfigWebhook.load();
81+
82+
expect(spy).toHaveBeenCalledWith(
83+
'Config loaded',
84+
expect.objectContaining({
85+
config: expect.objectContaining({
86+
webhookSecret: '***',
87+
}),
88+
}),
89+
);
90+
});
91+
});
92+
93+
describe('ConfigWebhook', () => {
94+
it('should load config successfully', async () => {
95+
process.env.REPOSITORY_ALLOW_LIST = '["repo1", "repo2"]';
96+
process.env.WORKFLOW_JOB_EVENT_SECONDARY_QUEUE = 'secondary-queue';
97+
process.env.PARAMETER_RUNNER_MATCHER_CONFIG_PATH = '/path/to/matcher/config';
98+
process.env.PARAMETER_GITHUB_APP_WEBHOOK_SECRET = '/path/to/webhook/secret';
99+
const matcherConfig = [
100+
{
101+
id: '1',
102+
arn: 'arn:aws:sqs:us-east-1:123456789012:queue1',
103+
fifo: false,
104+
matcherConfig: {
105+
labelMatchers: [['label1', 'label2']],
106+
exactMatch: true,
107+
},
108+
},
109+
];
110+
mocked(getParameter).mockImplementation(async (paramPath: string) => {
111+
if (paramPath === '/path/to/matcher/config') {
112+
return JSON.stringify(matcherConfig);
113+
}
114+
if (paramPath === '/path/to/webhook/secret') {
115+
return 'secret';
116+
}
117+
return '';
118+
});
119+
120+
const config: ConfigWebhook = await ConfigWebhook.load();
121+
122+
expect(config.repositoryAllowList).toEqual(['repo1', 'repo2']);
123+
expect(config.workflowJobEventSecondaryQueue).toBe('secondary-queue');
124+
expect(config.matcherConfig).toEqual(matcherConfig);
125+
expect(config.webhookSecret).toBe('secret');
126+
});
127+
128+
it('should load config successfully', async () => {
129+
process.env.PARAMETER_RUNNER_MATCHER_CONFIG_PATH = '/path/to/matcher/config';
130+
process.env.PARAMETER_GITHUB_APP_WEBHOOK_SECRET = '/path/to/webhook/secret';
131+
const matcherConfig = [
132+
{
133+
id: '1',
134+
arn: 'arn:aws:sqs:us-east-1:123456789012:queue1',
135+
fifo: false,
136+
matcherConfig: {
137+
labelMatchers: [['label1', 'label2']],
138+
exactMatch: true,
139+
},
140+
},
141+
];
142+
mocked(getParameter).mockImplementation(async (paramPath: string) => {
143+
if (paramPath === '/path/to/matcher/config') {
144+
return JSON.stringify(matcherConfig);
145+
}
146+
if (paramPath === '/path/to/webhook/secret') {
147+
return 'secret';
148+
}
149+
return '';
150+
});
151+
152+
const config: ConfigWebhook = await ConfigWebhook.load();
153+
154+
expect(config.repositoryAllowList).toEqual([]);
155+
expect(config.workflowJobEventSecondaryQueue).toBe('');
156+
expect(config.matcherConfig).toEqual(matcherConfig);
157+
expect(config.webhookSecret).toBe('secret');
158+
});
159+
160+
it('should throw error if config loading fails', async () => {
161+
process.env.PARAMETER_RUNNER_MATCHER_CONFIG_PATH = '/path/to/matcher/config';
162+
163+
mocked(getParameter).mockImplementation(async (paramPath: string) => {
164+
if (paramPath === '/path/to/matcher/config') {
165+
throw new Error('Failed to load matcher config');
166+
}
167+
return '';
168+
});
169+
170+
await expect(ConfigWebhook.load()).rejects.toThrow(
171+
'Failed to load config: Failed to load parameter for matcherConfig from path /path/to/matcher/config: Failed to load matcher config',
172+
);
173+
});
174+
});
175+
176+
describe('ConfigWebhookEventBridge', () => {
177+
it('should load config successfully', async () => {
178+
process.env.ALLOWED_EVENTS = '["push", "pull_request"]';
179+
process.env.EVENT_BUS_NAME = 'event-bus';
180+
process.env.PARAMETER_GITHUB_APP_WEBHOOK_SECRET = '/path/to/webhook/secret';
181+
182+
mocked(getParameter).mockImplementation(async (paramPath: string) => {
183+
if (paramPath === '/path/to/webhook/secret') {
184+
return 'secret';
185+
}
186+
return '';
187+
});
188+
189+
const config: ConfigWebhookEventBridge = await ConfigWebhookEventBridge.load();
190+
191+
expect(config.allowedEvents).toEqual(['push', 'pull_request']);
192+
expect(config.eventBusName).toBe('event-bus');
193+
expect(config.webhookSecret).toBe('secret');
194+
});
195+
196+
it('should throw error if config loading fails', async () => {
197+
mocked(getParameter).mockImplementation(async (paramPath: string) => {
198+
throw new Error(`Parameter ${paramPath} not found`);
199+
});
200+
201+
await expect(ConfigWebhookEventBridge.load()).rejects.toThrow(
202+
'Failed to load config: Environment variable for eventBusName is not set and no default value provided., Failed to load parameter for webhookSecret from path undefined: Parameter undefined not found',
203+
);
204+
});
205+
});
206+
207+
describe('ConfigDispatcher', () => {
208+
it('should load config successfully', async () => {
209+
process.env.REPOSITORY_ALLOW_LIST = '["repo1", "repo2"]';
210+
process.env.PARAMETER_RUNNER_MATCHER_CONFIG_PATH = '/path/to/matcher/config';
211+
212+
const matcherConfig = [
213+
{
214+
id: '1',
215+
arn: 'arn:aws:sqs:us-east-1:123456789012:queue1',
216+
fifo: false,
217+
matcherConfig: {
218+
labelMatchers: [['label1', 'label2']],
219+
exactMatch: true,
220+
},
221+
},
222+
];
223+
224+
mocked(getParameter).mockImplementation(async (paramPath: string) => {
225+
if (paramPath === '/path/to/matcher/config') {
226+
return JSON.stringify(matcherConfig);
227+
}
228+
return '';
229+
});
230+
231+
const config: ConfigDispatcher = await ConfigDispatcher.load();
232+
233+
expect(config.repositoryAllowList).toEqual(['repo1', 'repo2']);
234+
expect(config.matcherConfig).toEqual(matcherConfig);
235+
});
236+
237+
it('should throw error if config loading fails', async () => {
238+
mocked(getParameter).mockImplementation(async (paramPath: string) => {
239+
throw new Error(`Parameter ${paramPath} not found`);
240+
});
241+
242+
await expect(ConfigDispatcher.load()).rejects.toThrow(
243+
'Failed to load config: Failed to load parameter for matcherConfig from path undefined: Parameter undefined not found',
244+
);
245+
});
246+
});
247+
});

0 commit comments

Comments
 (0)