Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .tflint.hcl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
config {
format = "compact"
module = true
call_module_type = "local"
}

plugin "aws" {
Expand Down
4 changes: 0 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,8 @@ Talk to the forestkeepers in the `runners-channel` on Slack.
|------|------|
| [aws_sqs_queue.queued_builds](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource |
| [aws_sqs_queue.queued_builds_dlq](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource |
| [aws_sqs_queue.webhook_events_workflow_job_queue](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue) | resource |
| [aws_sqs_queue_policy.build_queue_dlq_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource |
| [aws_sqs_queue_policy.build_queue_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource |
| [aws_sqs_queue_policy.webhook_events_workflow_job_queue_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy) | resource |
| [random_string.random](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/string) | resource |
| [aws_iam_policy_document.deny_unsecure_transport](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |

Expand Down Expand Up @@ -156,7 +154,6 @@ Talk to the forestkeepers in the `runners-channel` on Slack.
| <a name="input_enable_ssm_on_runners"></a> [enable\_ssm\_on\_runners](#input\_enable\_ssm\_on\_runners) | Enable to allow access to the runner instances for debugging purposes via SSM. Note that this adds additional permissions to the runner instances. | `bool` | `false` | no |
| <a name="input_enable_user_data_debug_logging_runner"></a> [enable\_user\_data\_debug\_logging\_runner](#input\_enable\_user\_data\_debug\_logging\_runner) | Option to enable debug logging for user-data, this logs all secrets as well. | `bool` | `false` | no |
| <a name="input_enable_userdata"></a> [enable\_userdata](#input\_enable\_userdata) | Should the userdata script be enabled for the runner. Set this to false if you are using your own prebuilt AMI. | `bool` | `true` | no |
| <a name="input_enable_workflow_job_events_queue"></a> [enable\_workflow\_job\_events\_queue](#input\_enable\_workflow\_job\_events\_queue) | Enabling this experimental feature will create a secondary SQS queue to which a copy of the workflow\_job event will be delivered. | `bool` | `false` | no |
| <a name="input_eventbridge"></a> [eventbridge](#input\_eventbridge) | Enable the use of EventBridge by the module. By enabling this feature events will be put on the EventBridge by the webhook instead of directly dispatching to queues for scaling.<br/><br/> `enable`: Enable the EventBridge feature.<br/> `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. | <pre>object({<br/> enable = optional(bool, false)<br/> accept_events = optional(list(string), null)<br/> })</pre> | `{}` | no |
| <a name="input_ghes_ssl_verify"></a> [ghes\_ssl\_verify](#input\_ghes\_ssl\_verify) | GitHub Enterprise SSL verification. Set to 'false' when custom certificate (chains) is used for GitHub Enterprise Server (insecure). | `bool` | `true` | no |
| <a name="input_ghes_url"></a> [ghes\_url](#input\_ghes\_url) | GitHub Enterprise Server URL. Example: https://github.internal.co - DO NOT SET IF USING PUBLIC GITHUB | `string` | `null` | no |
Expand Down Expand Up @@ -251,7 +248,6 @@ Talk to the forestkeepers in the `runners-channel` on Slack.
| <a name="input_webhook_lambda_s3_object_version"></a> [webhook\_lambda\_s3\_object\_version](#input\_webhook\_lambda\_s3\_object\_version) | S3 object version for webhook lambda function. Useful if S3 versioning is enabled on source bucket. | `string` | `null` | no |
| <a name="input_webhook_lambda_timeout"></a> [webhook\_lambda\_timeout](#input\_webhook\_lambda\_timeout) | Time out of the webhook lambda in seconds. | `number` | `10` | no |
| <a name="input_webhook_lambda_zip"></a> [webhook\_lambda\_zip](#input\_webhook\_lambda\_zip) | File location of the webhook lambda zip file. | `string` | `null` | no |
| <a name="input_workflow_job_queue_configuration"></a> [workflow\_job\_queue\_configuration](#input\_workflow\_job\_queue\_configuration) | Configuration options for workflow job queue which is only applicable if the flag enable\_workflow\_job\_events\_queue is set to true. | <pre>object({<br/> delay_seconds = number<br/> visibility_timeout_seconds = number<br/> message_retention_seconds = number<br/> })</pre> | <pre>{<br/> "delay_seconds": null,<br/> "message_retention_seconds": null,<br/> "visibility_timeout_seconds": null<br/>}</pre> | no |

## Outputs

Expand Down
31 changes: 21 additions & 10 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,23 +332,34 @@ resource "aws_iam_role_policy" "event_rule_firehose_role" {

### Queue to publish workflow job events

!!! warning "Deprecated
!!! warning "Removed

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.
This feaTure will be removed since we introducing the EventBridge. Same functionality can be implemented by adding a rule to the EventBridge to forward `workflow_job` events to the SQS queue.

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.
Below an example how you can sent all `workflow_job` with action `in_progress` to a SQS queue.

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

Messages received on the queue are using the same format as published by GitHub wrapped in a property `workflowJobEvent`.
resource "aws_cloudwatch_event_rule" "workflow_job_in_progress" {
name = "workflow-job-in-progress"
event_bus_name = modules.runners.webhook.eventbridge.name # The name of the event bus output by the module

```
export interface GithubWorkflowEvent {
workflowJobEvent: WorkflowJobEvent;
event_pattern = <<EOF
{
"detail-type": ["workflow_job"],
"detail": {
"action": ["in_progress"]
}
}
EOF
}

resource "aws_sqs_queue" "workflow_job_in_progress" {
name = "workflow_job_in_progress
}

```

This extensible format allows more fields to be added if needed.
You can configure the queue by setting properties to `workflow_job_events_queue_config`


NOTE: By default, a runner AMI update requires a re-apply of this terraform config (the runner AMI ID is looked up by a terraform data source). To avoid this, you can use `ami_id_ssm_parameter_name` to have the scale-up lambda dynamically lookup the runner AMI ID from an SSM parameter at instance launch time. Said SSM parameter is managed outside of this module (e.g. by a runner AMI build workflow).
2 changes: 0 additions & 2 deletions examples/default/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,6 @@ module "runners" {

# override scaling down
scale_down_schedule_expression = "cron(* * * * ? *)"
# enable this flag to publish webhook events to workflow job queue
# enable_workflow_job_events_queue = true

enable_user_data_debug_logging_runner = true

Expand Down
3 changes: 0 additions & 3 deletions examples/multi-runner/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,6 @@ module "runners" {
# runner_binaries_syncer_lambda_zip = "../lambdas-download/runner-binaries-syncer.zip"
# runners_lambda_zip = "../lambdas-download/runners.zip"

# enable_workflow_job_events_queue = true
# override delay of events in seconds

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

Expand Down
5 changes: 2 additions & 3 deletions lambdas/functions/webhook/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import type { Config } from 'jest';

import defaultConfig from '../../jest.base.config';

const config: Config = {
...defaultConfig,
coverageThreshold: {
global: {
statements: 99.58,
statements: 100,
branches: 100,
functions: 100,
lines: 99.57,
lines: 100,
},
},
};
Expand Down
4 changes: 1 addition & 3 deletions lambdas/functions/webhook/src/ConfigLoader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,8 @@ describe('ConfigLoader Tests', () => {
describe('ConfigWebhook', () => {
it('should load config successfully', async () => {
process.env.REPOSITORY_ALLOW_LIST = '["repo1", "repo2"]';
process.env.SQS_WORKFLOW_JOB_QUEUE = 'secondary-queue';
process.env.PARAMETER_RUNNER_MATCHER_CONFIG_PATH = '/path/to/matcher/config';
process.env.PARAMETER_GITHUB_APP_WEBHOOK_SECRET = '/path/to/webhook/secret';
process.env.PARAMETER_RUNNER_MATCHER_CONFIG_PATH = '/path/to/matcher/config';
const matcherConfig = [
{
id: '1',
Expand All @@ -121,7 +120,6 @@ describe('ConfigLoader Tests', () => {
const config: ConfigWebhook = await ConfigWebhook.load();

expect(config.repositoryAllowList).toEqual(['repo1', 'repo2']);
expect(config.workflowJobEventSecondaryQueue).toBe('secondary-queue');
expect(config.matcherConfig).toEqual(matcherConfig);
expect(config.webhookSecret).toBe('secret');
});
Expand Down
2 changes: 0 additions & 2 deletions lambdas/functions/webhook/src/ConfigLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@ export class ConfigWebhook extends BaseConfig {

async loadConfig(): Promise<void> {
this.loadEnvVar(process.env.REPOSITORY_ALLOW_LIST, 'repositoryAllowList', []);
this.loadEnvVar(process.env.SQS_WORKFLOW_JOB_QUEUE, 'workflowJobEventSecondaryQueue', '');

await Promise.all([
this.loadParameter(process.env.PARAMETER_RUNNER_MATCHER_CONFIG_PATH, 'matcherConfig'),
Expand Down Expand Up @@ -129,7 +128,6 @@ export class ConfigDispatcher extends BaseConfig {

async loadConfig(): Promise<void> {
this.loadEnvVar(process.env.REPOSITORY_ALLOW_LIST, 'repositoryAllowList', []);
this.loadEnvVar(process.env.SQS_WORKFLOW_JOB_QUEUE, 'workflowJobEventSecondaryQueue', '');
await this.loadParameter(process.env.PARAMETER_RUNNER_MATCHER_CONFIG_PATH, 'matcherConfig');

validateRunnerMatcherConfig(this);
Expand Down
1 change: 0 additions & 1 deletion lambdas/functions/webhook/src/modules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,5 @@ declare namespace NodeJS {
REPOSITORY_ALLOW_LIST: string;
RUNNER_LABELS: string;
ACCEPT_EVENTS: string;
SQS_WORKFLOW_JOB_QUEUE: string;
}
}
7 changes: 0 additions & 7 deletions lambdas/functions/webhook/src/runners/dispatch.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { getParameter } from '@aws-github-runner/aws-ssm-util';

Check notice on line 1 in lambdas/functions/webhook/src/runners/dispatch.test.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

✅ No longer an issue: Code Duplication

The module no longer contains too many functions with similar structure
import { mocked } from 'jest-mock';
import nock from 'nock';
import { WorkflowJobEvent } from '@octokit/webhooks-types';
Expand All @@ -14,7 +14,6 @@
jest.mock('../sqs');
jest.mock('@aws-github-runner/aws-ssm-util');

const sendWebhookEventToWorkflowJobQueueMock = jest.mocked(sendActionRequest);
const GITHUB_APP_WEBHOOK_SECRET = 'TEST_SECRET';

const cleanEnv = process.env;
Expand Down Expand Up @@ -56,7 +55,6 @@
statusCode: 403,
});
expect(sendActionRequest).not.toHaveBeenCalled();
expect(sendWebhookEventToWorkflowJobQueueMock).not.toHaveBeenCalled();
});

it('should handle workflow_job events without installation id', async () => {
Expand All @@ -65,7 +63,6 @@
const resp = await dispatch(event, 'workflow_job', config);
expect(resp.statusCode).toBe(201);
expect(sendActionRequest).toHaveBeenCalled();
expect(sendWebhookEventToWorkflowJobQueueMock).toHaveBeenCalled();
});

it('should handle workflow_job events from allow listed repositories', async () => {
Expand All @@ -74,7 +71,6 @@
const resp = await dispatch(event, 'workflow_job', config);
expect(resp.statusCode).toBe(201);
expect(sendActionRequest).toHaveBeenCalled();
expect(sendWebhookEventToWorkflowJobQueueMock).toHaveBeenCalled();
});

it('should match labels', async () => {
Expand Down Expand Up @@ -108,7 +104,6 @@
queueFifo: false,
repoOwnerType: 'Organization',
});
expect(sendWebhookEventToWorkflowJobQueueMock).toHaveBeenCalled();
});

it('should sort matcher with exact first.', async () => {
Expand Down Expand Up @@ -157,7 +152,6 @@
queueFifo: false,
repoOwnerType: 'Organization',
});
expect(sendWebhookEventToWorkflowJobQueueMock).toHaveBeenCalled();
});

it('should not accept jobs where not all labels are supported (single matcher).', async () => {
Expand All @@ -181,7 +175,6 @@
const resp = await dispatch(event, 'workflow_job', config);
expect(resp.statusCode).toBe(202);
expect(sendActionRequest).not.toHaveBeenCalled();
expect(sendWebhookEventToWorkflowJobQueueMock).not.toHaveBeenCalled();
});
});

Expand Down
7 changes: 2 additions & 5 deletions lambdas/functions/webhook/src/runners/dispatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createChildLogger } from '@aws-github-runner/aws-powertools-util';
import { WorkflowJobEvent } from '@octokit/webhooks-types';

import { Response } from '../lambda';
import { RunnerMatcherConfig, sendActionRequest, sendWebhookEventToWorkflowJobQueue } from '../sqs';
import { RunnerMatcherConfig, sendActionRequest } from '../sqs';
import ValidationError from '../ValidationError';
import { ConfigDispatcher, ConfigWebhook } from '../ConfigLoader';

Expand All @@ -15,10 +15,7 @@ export async function dispatch(
): Promise<Response> {
validateRepoInAllowList(event, config);

const result = await handleWorkflowJob(event, eventType, config.matcherConfig!);
await sendWebhookEventToWorkflowJobQueue({ workflowJobEvent: event }, config);

return result;
return await handleWorkflowJob(event, eventType, config.matcherConfig!);
}

function validateRepoInAllowList(event: WorkflowJobEvent, config: ConfigDispatcher) {
Expand Down
68 changes: 1 addition & 67 deletions lambdas/functions/webhook/src/sqs/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
import { SendMessageCommandInput } from '@aws-sdk/client-sqs';

Check notice on line 1 in lambdas/functions/webhook/src/sqs/index.test.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

✅ No longer an issue: Code Duplication

The module no longer contains too many functions with similar structure

import { ActionRequestMessage, GithubWorkflowEvent, sendActionRequest, sendWebhookEventToWorkflowJobQueue } from '.';
import workflowjob_event from '../../test/resources/github_workflowjob_event.json';
import { getParameter } from '@aws-github-runner/aws-ssm-util';
import { mocked } from 'jest-mock';
import { ActionRequestMessage, sendActionRequest } from '.';

const mockSQS = {
sendMessage: jest.fn(() => {
Expand All @@ -15,9 +11,6 @@
}));
jest.mock('@aws-github-runner/aws-ssm-util');

import { SQS } from '@aws-sdk/client-sqs';
import { ConfigDispatcher, ConfigWebhook } from '../ConfigLoader';

describe('Test sending message to SQS.', () => {
const queueUrl = 'https://sqs.eu-west-1.amazonaws.com/123456789/queued-builds';
const message = {
Expand Down Expand Up @@ -72,62 +65,3 @@
await expect(result).resolves.not.toThrow();
});
});

describe('Test sending message to SQS.', () => {
const message: GithubWorkflowEvent = {
workflowJobEvent: JSON.parse(JSON.stringify(workflowjob_event)),
};
const sqsMessage: SendMessageCommandInput = {
QueueUrl: 'https://sqs.eu-west-1.amazonaws.com/123456789/webhook_events_workflow_job_queue',
MessageBody: JSON.stringify(message),
};
beforeEach(() => {
ConfigDispatcher.reset();
const mockedGet = mocked(getParameter);
mockedGet.mockResolvedValue('["abc"]');
});
afterEach(() => {
jest.clearAllMocks();
});

it('sends webhook events to workflow job queue', async () => {
// Arrange
process.env.SQS_WORKFLOW_JOB_QUEUE = sqsMessage.QueueUrl || '';
const config: ConfigWebhook = await ConfigWebhook.load();

// Act
const result = sendWebhookEventToWorkflowJobQueue(message, config);

// Assert
expect(mockSQS.sendMessage).toHaveBeenCalledWith(sqsMessage);
await expect(result).resolves.not.toThrow();
});

it('Does not send webhook events to workflow job event copy queue when job queue is not in environment', async () => {
// Arrange
process.env.SQS_WORKFLOW_JOB_QUEUE = '';
const config: ConfigDispatcher = await ConfigDispatcher.load();

// Act
await sendWebhookEventToWorkflowJobQueue(message, config);

// Assert
expect(SQS).not.toHaveBeenCalled();
});

it('Catch the exception when even copy queue throws exception', async () => {
// Arrange
process.env.SQS_WORKFLOW_JOB_QUEUE = sqsMessage.QueueUrl || '';
const config: ConfigDispatcher = await ConfigDispatcher.load();

const mockSQS = {
sendMessage: jest.fn(() => {
throw new Error();
}),
};
jest.mock('aws-sdk', () => ({
SQS: jest.fn().mockImplementation(() => mockSQS),
}));
await expect(sendWebhookEventToWorkflowJobQueue(message, config)).resolves.not.toThrow();
});
});
24 changes: 0 additions & 24 deletions lambdas/functions/webhook/src/sqs/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { SQS, SendMessageCommandInput } from '@aws-sdk/client-sqs';
import { WorkflowJobEvent } from '@octokit/webhooks-types';
import { createChildLogger, getTracedAWSV3Client } from '@aws-github-runner/aws-powertools-util';
import { ConfigDispatcher } from '../ConfigLoader';

const logger = createChildLogger('sqs');

Expand Down Expand Up @@ -49,26 +48,3 @@ export const sendActionRequest = async (message: ActionRequestMessage): Promise<

await sqs.sendMessage(sqsMessage);
};

export async function sendWebhookEventToWorkflowJobQueue(
message: GithubWorkflowEvent,
config: ConfigDispatcher,
): Promise<void> {
if (!config.workflowJobEventSecondaryQueue) {
return;
}

const sqs = new SQS({ region: process.env.AWS_REGION });
const sqsMessage: SendMessageCommandInput = {
QueueUrl: String(config.workflowJobEventSecondaryQueue),
MessageBody: JSON.stringify(message),
};

logger.info(`Sending event to the workflow job queue: ${config.workflowJobEventSecondaryQueue}`);

try {
await sqs.sendMessage(sqsMessage);
} catch (e) {
logger.warn(`Error in sending webhook events to workflow job queue: ${(e as Error).message}`);
}
}
Loading