Skip to content

Commit 5f1f0fe

Browse files
committed
Introduce object to configure eventbridge
1 parent 0c8e93f commit 5f1f0fe

File tree

19 files changed

+179
-84
lines changed

19 files changed

+179
-84
lines changed

docs/configuration.md

+76-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ To be able to support a number of use-cases, the module has quite a lot of confi
66

77
- Org vs Repo level. You can configure the module to connect the runners in GitHub on an org level and share the runners in your org, or set the runners on repo level and the module will install the runner to the repo. There can be multiple repos but runners are not shared between repos.
88
- Multi-Runner module. This modules allows you to create multiple runner configurations with a single webhook and single GitHub App to simplify deployment of different types of runners. Check the detailed module [documentation](modules/public/multi-runner.md) for more information or checkout the [multi-runner example](examples/multi-runner.md).
9-
- Webhook mode, the module can be deployed in the mode `direct` and `eventbridge`. The `direct` mode is the default and will directly distribute to SQS for the scale-up lambda. The `eventbridge` mode will publish the event to an event bus with a target rule the events are sent to a dispatch lambda. The dispatch lambda will send the event to the SQS queue. The `eventbridge` mode is useful when you want to have more control over the events and potentially filter them. The `eventbridge` mode is disabled by default. We expect thhe `eventbridge` mode will be the future direction to build a data lake, build metrics, acto on `workflow_job` job started events, etc.
9+
- Webhook mode, the module can be deployed in the mode `direct` and `eventbridge` (Experimental). The `direct` mode is the default and will directly distribute to SQS for the scale-up lambda. The `eventbridge` mode will publish the event to an event bus with a target rule the events are sent to a dispatch lambda. The dispatch lambda will send the event to the SQS queue. The `eventbridge` mode is useful when you want to have more control over the events and potentially filter them. The `eventbridge` mode is disabled by default. We expect thhe `eventbridge` mode will be the future direction to build a data lake, build metrics, acto on `workflow_job` job started events, etc.
1010
- Linux vs Windows. You can configure the OS types linux and win. Linux will be used by default.
1111
- Re-use vs Ephemeral. By default runners are re-used, until detected idle. Once idle they will be removed from the pool. To improve security we are introducing ephemeral runners. Those runners are only used for one job. Ephemeral runners only work in combination with the workflow job event. For ephemeral runners the lambda requests a JIT (just in time) configuration via the GitHub API to register the runner. [JIT configuration](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-just-in-time-runners) is limited to ephemeral runners (and currently not supported by GHES). For non-ephemeral runners, a registration token is always requested. In both cases the configuration is made available to the instance via the same SSM parameter. To disable JIT configuration for ephemeral runners set `enable_jit_config` to `false`. We also suggest using a pre-build AMI to improve the start time of jobs for ephemeral runners.
1212
- Job retry (**Beta**). By default the scale-up lambda will discard the message when it is handled. Meaning in the ephemeral use-case an instance is created. The created runner will ask GitHub for a job, no guarantee it will run the job for which it was scaling. Result could be that with small system hick-up the job is keeping waiting for a runner. Enable a pool (org runners) is one option to avoid this problem. Another option is to enable the job retry function. Which will retry the job after a delay for a configured number of times.
@@ -259,8 +259,83 @@ Below an example of the the log messages created.
259259
}
260260
```
261261

262+
### EventBridge
263+
264+
The module can be deployed in the mode `eventbridge` (Experimental). The `eventbridge` mode will publish the event to an event bus with a target rule the events are sent to a dispatch lambda. The dispatch lambda will send the event to the SQS queue. The `eventbridge` mode is disabled by default. We expect thhe `eventbridge` mode will be the future direction to build a data lake, build metrics, acto on `workflow_job` job started events, etc.
265+
266+
Example to use the EventBridge:
267+
268+
```hcl
269+
270+
module "runners" {
271+
source = "philips-labs/github-runners/aws"
272+
273+
...
274+
eventbridge = {
275+
enable = true
276+
}
277+
...
278+
}
279+
280+
locals {
281+
event_bus_name = module.runners.webhook.eventbridge.event_bus.name
282+
}
283+
284+
resource "aws_cloudwatch_event_rule" "example" {
285+
name = "${local.prefix}-github-events-all"
286+
description = "Caputure all GitHub events"
287+
event_bus_name = local.event_bus_name
288+
event_pattern = <<EOF
289+
{
290+
"source": [{
291+
"prefix": "github"
292+
}]
293+
}
294+
EOF
295+
}
296+
297+
resource "aws_cloudwatch_event_target" "main" {
298+
rule = aws_cloudwatch_event_rule.example.name
299+
arn = <arn of target>
300+
event_bus_name = local.event_bus_name
301+
role_arn = aws_iam_role.event_rule_firehose_role.arn
302+
}
303+
304+
data "aws_iam_policy_document" "event_rule_firehose_role" {
305+
statement {
306+
actions = ["sts:AssumeRole"]
307+
308+
principals {
309+
type = "Service"
310+
identifiers = ["events.amazonaws.com"]
311+
}
312+
}
313+
}
314+
315+
resource "aws_iam_role" "event_rule_role" {
316+
name = "${local.prefix}-eventbridge-github-rule"
317+
assume_role_policy = data.aws_iam_policy_document.event_rule_firehose_role.json
318+
}
319+
320+
data aws_iam_policy_document firehose_stream {
321+
statement {
322+
INSER_YOUR_POIICY_HERE_TO_ACCESS_THE_TARGET
323+
}
324+
}
325+
326+
resource "aws_iam_role_policy" "event_rule_firehose_role" {
327+
name = "target-event-rule-firehose"
328+
role = aws_iam_role.event_rule_firehose_role.name
329+
policy = data.aws_iam_policy_document.firehose_stream.json
330+
}
331+
```
332+
262333
### Queue to publish workflow job events
263334

335+
!!! warning "Deprecated
336+
337+
This fearure will be removed since we introducing the EventBridge. Same functinallity can be implemented by adding a rule to the EventBridge to forward `workflow_job` events to the SQS queue.
338+
264339
This queue is an experimental feature to allow you to receive a copy of the wokflow_jobs events sent by the GitHub App. This can be used to calculate a matrix or monitor the system.
265340

266341
To enable the feature set `enable_workflow_job_events_queue = true`. Be aware though, this feature is experimental!

examples/default/main.tf

+6-2
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,12 @@ module "runners" {
9797
# prefix GitHub runners with the environment name
9898
runner_name_prefix = "${local.environment}_"
9999

100-
# webhook supports to modes, either direct or via the eventbridge
101-
# webhook_mode = "direct" # or "eventbridge"
100+
# webhook supports to modes, either direct or via the eventbridge, uncommet to enable eventbridge
101+
# eventbridge = {
102+
# enable = true
103+
# # adjust the allow events to only allow specific events, like workflow_job
104+
# # allowed_events = ['workflow_job']
105+
# }
102106

103107
# Enable debug logging for the lambda functions
104108
# log_level = "debug"

examples/multi-runner/main.tf

+6-4
Original file line numberDiff line numberDiff line change
@@ -78,10 +78,12 @@ module "runners" {
7878
webhook_secret = random_id.random.hex
7979
}
8080

81-
# Deploy webhook in EventBridge mode
82-
webhook_mode = "eventbridge"
83-
# adjust the allow events to only allow specific events, like workflow_job
84-
# eventbridge_allowed_events = ['workflow_job']
81+
# Deploy webhook using the EventBridge
82+
eventbridge = {
83+
enable = true
84+
# adjust the allow events to only allow specific events, like workflow_job
85+
accept_events = ["workflow_job"]
86+
}
8587

8688
# enable this section for tracing
8789
# tracing_config = {

lambdas/functions/webhook/src/ConfigLoader.test.ts

+28-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ describe('ConfigLoader Tests', () => {
176176

177177
describe('ConfigWebhookEventBridge', () => {
178178
it('should load config successfully', async () => {
179-
process.env.ALLOWED_EVENTS = '["push", "pull_request"]';
179+
process.env.ACCEPT_EVENTS = '["push", "pull_request"]';
180180
process.env.EVENT_BUS_NAME = 'event-bus';
181181
process.env.PARAMETER_GITHUB_APP_WEBHOOK_SECRET = '/path/to/webhook/secret';
182182

@@ -244,6 +244,33 @@ describe('ConfigLoader Tests', () => {
244244
);
245245
});
246246

247+
it('should rely on default when optionals are not set.', async () => {
248+
process.env.ACCEPT_EVENTS = 'null';
249+
process.env.PARAMETER_RUNNER_MATCHER_CONFIG_PATH = '/path/to/matcher/config';
250+
const matcherConfig: RunnerMatcherConfig[] = [
251+
{
252+
arn: 'arn:aws:sqs:eu-central-1:123456:npalm-default-queued-builds',
253+
fifo: true,
254+
id: 'https://sqs.eu-central-1.amazonaws.com/123456/npalm-default-queued-builds',
255+
matcherConfig: {
256+
exactMatch: true,
257+
labelMatchers: [['default', 'example', 'linux', 'self-hosted', 'x64']],
258+
},
259+
},
260+
];
261+
mocked(getParameter).mockImplementation(async (paramPath: string) => {
262+
if (paramPath === '/path/to/matcher/config') {
263+
return JSON.stringify(matcherConfig);
264+
}
265+
return '';
266+
});
267+
268+
const config: ConfigDispatcher = await ConfigDispatcher.load();
269+
270+
expect(config.repositoryAllowList).toEqual([]);
271+
expect(config.matcherConfig).toEqual(matcherConfig);
272+
});
273+
247274
it('should throw an error if runner matcher config is empty.', async () => {
248275
process.env.REPOSITORY_ALLOW_LIST = '["repo1", "repo2"]';
249276
process.env.PARAMETER_RUNNER_MATCHER_CONFIG_PATH = '/path/to/matcher/config';

lambdas/functions/webhook/src/ConfigLoader.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ abstract class BaseConfig {
4444

4545
protected loadEnvVar<T>(envVar: string, propertyName: keyof this, defaultValue?: T): void {
4646
logger.debug(`Loading env var for ${String(propertyName)}`, { envVar });
47-
if (envVar !== undefined) {
47+
if (!(envVar == undefined || envVar === 'null')) {
4848
this.loadProperty(propertyName, envVar);
4949
} else if (defaultValue !== undefined) {
5050
this[propertyName] = defaultValue as unknown as this[keyof this];
@@ -113,7 +113,7 @@ export class ConfigWebhookEventBridge extends BaseConfig {
113113
webhookSecret: string = '';
114114

115115
async loadConfig(): Promise<void> {
116-
this.loadEnvVar(process.env.ALLOWED_EVENTS, 'allowedEvents', []);
116+
this.loadEnvVar(process.env.ACCEPT_EVENTS, 'allowedEvents', []);
117117
this.loadEnvVar(process.env.EVENT_BUS_NAME, 'eventBusName');
118118
await this.loadParameter(process.env.PARAMETER_GITHUB_APP_WEBHOOK_SECRET, 'webhookSecret');
119119

lambdas/functions/webhook/src/modules.d.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ declare namespace NodeJS {
66
PARAMETER_RUNNER_MATCHER_CONFIG_PATH: string;
77
REPOSITORY_ALLOW_LIST: string;
88
RUNNER_LABELS: string;
9-
ALLOWED_EVENTS: string;
9+
ACCEPT_EVENTS: string;
1010
SQS_WORKFLOW_JOB_QUEUE: string;
1111
}
1212
}

lambdas/functions/webhook/src/webhook/index.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ describe('handle GitHub webhook events', () => {
196196
});
197197

198198
ConfigWebhookEventBridge.reset();
199-
process.env.ALLOWED_EVENTS = JSON.stringify(input.events);
199+
process.env.ACCEPT_EVENTS = JSON.stringify(input.events);
200200
config = await ConfigWebhookEventBridge.load();
201201

202202
await expect(
@@ -239,7 +239,7 @@ describe('handle GitHub webhook events', () => {
239239
});
240240

241241
ConfigWebhookEventBridge.reset();
242-
process.env.ALLOWED_EVENTS = JSON.stringify(input.events);
242+
process.env.ACCEPT_EVENTS = JSON.stringify(input.events);
243243
config = await ConfigWebhookEventBridge.load();
244244

245245
await expect(

main.tf

+1-3
Original file line numberDiff line numberDiff line change
@@ -124,16 +124,14 @@ module "ssm" {
124124
module "webhook" {
125125
source = "./modules/webhook"
126126

127-
mode = var.webhook_mode
128-
eventbridge_allowed_events = var.eventbridge_allowed_events
129-
130127
ssm_paths = {
131128
root = local.ssm_root_path
132129
webhook = var.ssm_paths.webhook
133130
}
134131
prefix = var.prefix
135132
tags = local.tags
136133
kms_key_arn = var.kms_key_arn
134+
eventbridge = var.eventbridge
137135

138136
runner_matcher_config = {
139137
(aws_sqs_queue.queued_builds.id) = {

modules/multi-runner/outputs.tf

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ output "webhook" {
3939
lambda_role = module.webhook.role
4040
endpoint = "${module.webhook.gateway.api_endpoint}/${module.webhook.endpoint_relative_path}"
4141
webhook = module.webhook.webhook
42-
dispatcher = var.webhook_mode == "eventbridge" ? module.webhook.dispatcher : null
43-
eventbridge = var.webhook_mode == "eventbridge" ? module.webhook.eventbridge : null
42+
dispatcher = var.eventbridge.enable ? module.webhook.dispatcher : null
43+
eventbridge = var.eventbridge.enable ? module.webhook.eventbridge : null
4444
}
4545
}
4646

modules/multi-runner/variables.tf

+10-18
Original file line numberDiff line numberDiff line change
@@ -271,24 +271,6 @@ variable "lambda_s3_bucket" {
271271
default = null
272272
}
273273

274-
275-
variable "webhook_mode" {
276-
description = "The webhook and dispatching to runner queues supports two modes. Direct messages, are delivered directly to the runner queues. EventBridge messages are delivered to an EventBridge bus and then dispatched to the runner queues. Valid values are `direct` and `eventbridge`."
277-
type = string
278-
default = "direct"
279-
280-
validation {
281-
condition = contains(["direct", "eventbridge"], var.webhook_mode)
282-
error_message = "`mode` value is not valid, valid values are: `direct`, and `eventbridge`."
283-
}
284-
}
285-
286-
variable "eventbridge_allowed_events" {
287-
description = "List of events that are allowed (accepted) to be sent to the eventbridge by the webhook. Variable only have effect if `webhook_mode` is set to `eventbridge`."
288-
type = list(string)
289-
default = []
290-
}
291-
292274
variable "webhook_lambda_s3_key" {
293275
description = "S3 key for webhook lambda function. Required if using S3 bucket to specify lambdas."
294276
type = string
@@ -701,3 +683,13 @@ variable "metrics" {
701683
})
702684
default = {}
703685
}
686+
687+
variable "eventbridge" {
688+
description = "Enable the use of EventBridge by the module. By enable this feature events will be putted on the EventBridge bhy the webhook instead of directly dispatchting to queues for sacling."
689+
type = object({
690+
enable = optional(bool, false)
691+
accept_events = optional(list(string), [])
692+
})
693+
694+
default = {}
695+
}

modules/multi-runner/webhook.tf

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
module "webhook" {
2-
source = "../webhook"
3-
prefix = var.prefix
4-
tags = local.tags
5-
mode = var.webhook_mode
6-
eventbridge_allowed_events = var.eventbridge_allowed_events
7-
kms_key_arn = var.kms_key_arn
8-
2+
source = "../webhook"
3+
prefix = var.prefix
4+
tags = local.tags
5+
kms_key_arn = var.kms_key_arn
6+
eventbridge = var.eventbridge
97
runner_matcher_config = local.runner_config
108
matcher_config_parameter_store_tier = var.matcher_config_parameter_store_tier
9+
1110
ssm_paths = {
1211
root = local.ssm_root_path
1312
webhook = var.ssm_paths.webhook

modules/webhook/eventbridge/variables.tf

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,6 @@ variable "config" {
5050
arn = string
5151
version = string
5252
})
53-
allowed_events = optional(list(string), [])
53+
accept_events = optional(list(string), null)
5454
})
5555
}

modules/webhook/eventbridge/webhook.tf

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ resource "aws_lambda_function" "webhook" {
2222
POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.config.tracing_config.capture_http_requests
2323
POWERTOOLS_TRACER_CAPTURE_ERROR = var.config.tracing_config.capture_error
2424
# Parameters required for lambda configuration
25-
ALLOWED_EVENTS = jsonencode(var.config.allowed_events)
25+
ACCEPT_EVENTS = jsonencode(var.config.accept_events)
2626
EVENT_BUS_NAME = aws_cloudwatch_event_bus.main.name
2727
PARAMETER_GITHUB_APP_WEBHOOK_SECRET = var.config.github_app_parameters.webhook_secret.name
2828
PARAMETER_RUNNER_MATCHER_CONFIG_PATH = var.config.ssm_parameter_runner_matcher_config.name

modules/webhook/main.tf

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,5 +62,5 @@ resource "aws_apigatewayv2_integration" "webhook" {
6262
connection_type = "INTERNET"
6363
description = "GitHub App webhook for receiving build events."
6464
integration_method = "POST"
65-
integration_uri = var.mode == "direct" ? module.direct[0].webhook.lambda.invoke_arn : module.eventbridge[0].webhook.lambda.invoke_arn
65+
integration_uri = !var.eventbridge.enable ? module.direct[0].webhook.lambda.invoke_arn : module.eventbridge[0].webhook.lambda.invoke_arn
6666
}

modules/webhook/outputs.tf

+6-6
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,27 @@ output "endpoint_relative_path" {
77
}
88

99
output "webhook" {
10-
value = var.mode == "direct" ? module.direct[0].webhook : module.eventbridge[0].webhook
10+
value = !var.eventbridge.enable ? module.direct[0].webhook : module.eventbridge[0].webhook
1111
}
1212

1313
output "dispatcher" {
14-
value = var.mode == "eventbridge" ? module.eventbridge[0].dispatcher : null
14+
value = var.eventbridge.enable ? module.eventbridge[0].dispatcher : null
1515
}
1616

1717
output "eventbridge" {
18-
value = var.mode == "eventbridge" ? module.eventbridge[0].eventbridge : null
18+
value = var.eventbridge.enable ? module.eventbridge[0].eventbridge : null
1919
}
2020

2121
### For backwards compatibility
2222

2323
output "lambda" {
24-
value = var.mode == "direct" ? module.direct[0].webhook.lambda : module.eventbridge[0].webhook.lambda
24+
value = !var.eventbridge.enable ? module.direct[0].webhook.lambda : module.eventbridge[0].webhook.lambda
2525
}
2626

2727
output "lambda_log_group" {
28-
value = var.mode == "direct" ? module.direct[0].webhook.log_group : module.eventbridge[0].webhook.log_group
28+
value = !var.eventbridge.enable ? module.direct[0].webhook.log_group : module.eventbridge[0].webhook.log_group
2929
}
3030

3131
output "role" {
32-
value = var.mode == "direct" ? module.direct[0].webhook.role : module.eventbridge[0].webhook.role
32+
value = !var.eventbridge.enable ? module.direct[0].webhook.role : module.eventbridge[0].webhook.role
3333
}

modules/webhook/variables.tf

+12-14
Original file line numberDiff line numberDiff line change
@@ -211,18 +211,16 @@ variable "matcher_config_parameter_store_tier" {
211211
}
212212
}
213213

214-
variable "mode" {
215-
description = "The webhook and dispatching to runner queues supports two modes. Direct messages, are delivered directly to the runner queues. EventBridge messages are delivered to an EventBridge bus and then dispatched to the runner queues. Valid values are `direct` and `eventbridge`."
216-
type = string
217-
218-
validation {
219-
condition = contains(["direct", "eventbridge"], var.mode)
220-
error_message = "`mode` value is not valid, valid values are: `direct`, and `eventbridge`."
221-
}
222-
}
223-
224-
variable "eventbridge_allowed_events" {
225-
description = "List of events that are allowed (accepted) to be sent to the eventbridge by the webhook."
226-
type = list(string)
227-
default = []
214+
variable "eventbridge" {
215+
description = <<EOF
216+
Enable the use of EventBridge by the module. By enable this feature events will be putted on the EventBridge bhy the
217+
webhook instead of directly dispatchting to queues for sacling.
218+
219+
`enable`: Enable the EventBridge feature.
220+
`accept_events`: List can be used to only allow specific events to be putted on the EventBridge. By default all events, empty list will be be interpreted as all events.
221+
EOF
222+
type = object({
223+
enable = optional(bool, false)
224+
accept_events = optional(list(string), null)
225+
})
228226
}

0 commit comments

Comments
 (0)