Skip to content

Commit

Permalink
feat: Add option to match some of the labes instead of all #2122 (#2123)
Browse files Browse the repository at this point in the history
* feat: add some behavior

force pushed to make conventionalcommits happy

* run prettier

* add WORKFLOW_JOB_LABELS_CHECK_ALL to tests
  • Loading branch information
sblack4 authored Jun 23, 2022
1 parent ca11f6b commit c5e3c21
Show file tree
Hide file tree
Showing 8 changed files with 76 additions and 6 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ In case the setup does not work as intended follow the trace of events:
| <a name="input_runner_ec2_tags"></a> [runner\_ec2\_tags](#input\_runner\_ec2\_tags) | Map of tags that will be added to the launch template instance tag specificatons. | `map(string)` | `{}` | no |
| <a name="input_runner_egress_rules"></a> [runner\_egress\_rules](#input\_runner\_egress\_rules) | List of egress rules for the GitHub runner instances. | <pre>list(object({<br> cidr_blocks = list(string)<br> ipv6_cidr_blocks = list(string)<br> prefix_list_ids = list(string)<br> from_port = number<br> protocol = string<br> security_groups = list(string)<br> self = bool<br> to_port = number<br> description = string<br> }))</pre> | <pre>[<br> {<br> "cidr_blocks": [<br> "0.0.0.0/0"<br> ],<br> "description": null,<br> "from_port": 0,<br> "ipv6_cidr_blocks": [<br> "::/0"<br> ],<br> "prefix_list_ids": null,<br> "protocol": "-1",<br> "security_groups": null,<br> "self": null,<br> "to_port": 0<br> }<br>]</pre> | no |
| <a name="input_runner_enable_workflow_job_labels_check"></a> [runner\_enable\_workflow\_job\_labels\_check](#input\_runner\_enable\_workflow\_job\_labels\_check) | If set to true all labels in the workflow job even are matched agaist the custom labels and GitHub labels (os, architecture and `self-hosted`). When the labels are not matching the event is dropped at the webhook. | `bool` | `false` | no |
| <a name="input_runner_enable_workflow_job_labels_check_all"></a> [runner\_enable\_workflow\_job\_labels\_check\_all](#input\_runner\_enable\_workflow\_job\_labels\_check\_all) | If set to true all labels in the workflow job must match the GitHub labels (os, architecture and `self-hosted`). When false if __any__ label matches it will trigger the webhook. `runner_enable_workflow_job_labels_check` must be true for this to take effect. | `bool` | `true` | no |
| <a name="input_runner_extra_labels"></a> [runner\_extra\_labels](#input\_runner\_extra\_labels) | Extra (custom) labels for the runners (GitHub). Separate each label by a comma. Labels checks on the webhook can be enforced by setting `enable_workflow_job_labels_check`. GitHub read-only labels should not be provided. | `string` | `""` | no |
| <a name="input_runner_group_name"></a> [runner\_group\_name](#input\_runner\_group\_name) | Name of the runner group. | `string` | `"Default"` | no |
| <a name="input_runner_iam_role_managed_policy_arns"></a> [runner\_iam\_role\_managed\_policy\_arns](#input\_runner\_iam\_role\_managed\_policy\_arns) | Attach AWS or customer-managed IAM policies (by ARN) to the runner IAM role | `list(string)` | `[]` | no |
Expand Down
1 change: 1 addition & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ module "webhook" {

# labels
enable_workflow_job_labels_check = var.runner_enable_workflow_job_labels_check
workflow_job_labels_check_all = var.runner_enable_workflow_job_labels_check_all
runner_labels = "self-hosted,${var.runner_os},${var.runner_architecture},${var.runner_extra_labels}"

role_path = var.role_path
Expand Down
1 change: 1 addition & 0 deletions modules/webhook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ No modules.
| <a name="input_tags"></a> [tags](#input\_tags) | Map of tags that will be added to created resources. By default resources will be tagged with name and environment. | `map(string)` | `{}` | no |
| <a name="input_webhook_lambda_s3_key"></a> [webhook\_lambda\_s3\_key](#input\_webhook\_lambda\_s3\_key) | S3 key for webhook lambda function. Required if using S3 bucket to specify lambdas. | `any` | `null` | no |
| <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. | `any` | `null` | no |
| <a name="input_workflow_job_labels_check_all"></a> [workflow\_job\_labels\_check\_all](#input\_workflow\_job\_labels\_check\_all) | If set to true all labels in the workflow job must match the GitHub labels (os, architecture and `self-hosted`). When false if __any__ label matches it will trigger the webhook. `enable_workflow_job_labels_check` must be true for this to take effect. | `bool` | `true` | no |

## Outputs

Expand Down
42 changes: 42 additions & 0 deletions modules/webhook/lambdas/webhook/src/webhook/handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ describe('handler', () => {
it('Check runner labels accept test job', async () => {
process.env.RUNNER_LABELS = '["self-hosted", "test"]';
process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK = 'true';
process.env.WORKFLOW_JOB_LABELS_CHECK_ALL = 'true';
const event = JSON.stringify({
...workflowjob_event,
workflow_job: {
Expand All @@ -142,6 +143,7 @@ describe('handler', () => {
it('Check runner labels accept job with mixed order.', async () => {
process.env.RUNNER_LABELS = '["linux", "test", "self-hosted"]';
process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK = 'true';
process.env.WORKFLOW_JOB_LABELS_CHECK_ALL = 'true';
const event = JSON.stringify({
...workflowjob_event,
workflow_job: {
Expand All @@ -160,6 +162,7 @@ describe('handler', () => {
it('Check webhook does not accept jobs where not all labels are provided in job.', async () => {
process.env.RUNNER_LABELS = '["self-hosted", "test", "test2"]';
process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK = 'true';
process.env.WORKFLOW_JOB_LABELS_CHECK_ALL = 'true';
const event = JSON.stringify({
...workflowjob_event,
workflow_job: {
Expand All @@ -178,6 +181,7 @@ describe('handler', () => {
it('Check webhook does not accept jobs where not all labels are supported by the runner.', async () => {
process.env.RUNNER_LABELS = '["self-hosted", "x64", "linux", "test"]';
process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK = 'true';
process.env.WORKFLOW_JOB_LABELS_CHECK_ALL = 'true';
const event = JSON.stringify({
...workflowjob_event,
workflow_job: {
Expand All @@ -192,6 +196,44 @@ describe('handler', () => {
expect(resp.statusCode).toBe(202);
expect(sendActionRequest).not.toBeCalled;
});

it('Check webhook will accept jobs with a single acceptable label.', async () => {
process.env.RUNNER_LABELS = '["self-hosted", "x64", "linux", "test"]';
process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK = 'true';
process.env.WORKFLOW_JOB_LABELS_CHECK_ALL = 'false';
const event = JSON.stringify({
...workflowjob_event,
workflow_job: {
...workflowjob_event.workflow_job,
labels: ['x64'],
},
});
const resp = await handle(
{ 'X-Hub-Signature': await webhooks.sign(event), 'X-GitHub-Event': 'workflow_job' },
event,
);
expect(resp.statusCode).toBe(201);
expect(sendActionRequest).toBeCalled();
});

it('Check webhook will not accept jobs without correct label when job label check all is false.', async () => {
process.env.RUNNER_LABELS = '["self-hosted", "x64", "linux", "test"]';
process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK = 'true';
process.env.WORKFLOW_JOB_LABELS_CHECK_ALL = 'false';
const event = JSON.stringify({
...workflowjob_event,
workflow_job: {
...workflowjob_event.workflow_job,
labels: ['ubuntu-latest'],
},
});
const resp = await handle(
{ 'X-Hub-Signature': await webhooks.sign(event), 'X-GitHub-Event': 'workflow_job' },
event,
);
expect(resp.statusCode).toBe(202);
expect(sendActionRequest).not.toBeCalled;
});
});

describe('Test for check_run event (legacy): ', () => {
Expand Down
24 changes: 18 additions & 6 deletions modules/webhook/lambdas/webhook/src/webhook/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const supportedEvents = ['check_run', 'workflow_job'];
const logger = rootLogger.getChildLogger();

export async function handle(headers: IncomingHttpHeaders, body: string): Promise<Response> {
const { environment, repositoryWhiteList, enableWorkflowLabelCheck, runnerLabels } = readEnvironmentVariables();
const { environment, repositoryWhiteList, enableWorkflowLabelCheck, workflowLabelCheckAll, runnerLabels } =
readEnvironmentVariables();

// ensure header keys lower case since github headers can contain capitals.
for (const key in headers) {
Expand Down Expand Up @@ -66,6 +67,7 @@ export async function handle(headers: IncomingHttpHeaders, body: string): Promis
payload as WorkflowJobEvent,
githubEvent,
enableWorkflowLabelCheck,
workflowLabelCheckAll,
runnerLabels,
);
} else if (githubEvent == 'check_run') {
Expand All @@ -79,11 +81,13 @@ function readEnvironmentVariables() {
const environment = process.env.ENVIRONMENT;
const enableWorkflowLabelCheckEnv = process.env.ENABLE_WORKFLOW_JOB_LABELS_CHECK || 'false';
const enableWorkflowLabelCheck = JSON.parse(enableWorkflowLabelCheckEnv) as boolean;
const workflowLabelCheckAllEnv = process.env.WORKFLOW_JOB_LABELS_CHECK_ALL || 'false';
const workflowLabelCheckAll = JSON.parse(workflowLabelCheckAllEnv) as boolean;
const repositoryWhiteListEnv = process.env.REPOSITORY_WHITE_LIST || '[]';
const repositoryWhiteList = JSON.parse(repositoryWhiteListEnv) as Array<string>;
const runnerLabelsEnv = process.env.RUNNER_LABELS || '[]';
const runnerLabels = JSON.parse(runnerLabelsEnv) as Array<string>;
return { environment, repositoryWhiteList, enableWorkflowLabelCheck, runnerLabels };
return { environment, repositoryWhiteList, enableWorkflowLabelCheck, workflowLabelCheckAll, runnerLabels };
}

async function verifySignature(
Expand Down Expand Up @@ -117,9 +121,10 @@ async function handleWorkflowJob(
body: WorkflowJobEvent,
githubEvent: string,
enableWorkflowLabelCheck: boolean,
workflowLabelCheckAll: boolean,
runnerLabels: string[],
): Promise<Response> {
if (enableWorkflowLabelCheck && !canRunJob(body, runnerLabels)) {
if (enableWorkflowLabelCheck && !canRunJob(body, runnerLabels, workflowLabelCheckAll)) {
logger.warn(
`Received event contains runner labels '${body.workflow_job.labels}' that are not accepted.`,
LogFields.print(),
Expand Down Expand Up @@ -171,10 +176,17 @@ function isRepoNotAllowed(repoFullName: string, repositoryWhiteList: string[]):
return repositoryWhiteList.length > 0 && !repositoryWhiteList.includes(repoFullName);
}

function canRunJob(job: WorkflowJobEvent, runnerLabels: string[]): boolean {
function canRunJob(job: WorkflowJobEvent, runnerLabels: string[], workflowLabelCheckAll: boolean): boolean {
const workflowJobLabels = job.workflow_job.labels;
const runnerMatch = runnerLabels.every((l) => workflowJobLabels.includes(l));
const jobMatch = workflowJobLabels.every((l) => runnerLabels.includes(l));
let runnerMatch;
let jobMatch;
if (workflowLabelCheckAll) {
runnerMatch = runnerLabels.every((l) => workflowJobLabels.includes(l));
jobMatch = workflowJobLabels.every((l) => runnerLabels.includes(l));
} else {
runnerMatch = runnerLabels.some((l) => workflowJobLabels.includes(l));
jobMatch = workflowJobLabels.some((l) => runnerLabels.includes(l));
}
const match = jobMatch && runnerMatch;

logger.debug(
Expand Down
6 changes: 6 additions & 0 deletions modules/webhook/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ variable "enable_workflow_job_labels_check" {
default = false
}

variable "workflow_job_labels_check_all" {
description = "If set to true all labels in the workflow job must match the GitHub labels (os, architecture and `self-hosted`). When false if __any__ label matches it will trigger the webhook. `enable_workflow_job_labels_check` must be true for this to take effect."
type = bool
default = true
}

variable "log_type" {
description = "Logging format for lambda logging. Valid values are 'json', 'pretty', 'hidden'. "
type = string
Expand Down
1 change: 1 addition & 0 deletions modules/webhook/webhook.tf
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ resource "aws_lambda_function" "webhook" {
environment {
variables = {
ENABLE_WORKFLOW_JOB_LABELS_CHECK = var.enable_workflow_job_labels_check
WORKFLOW_JOB_LABELS_CHECK_ALL = var.workflow_job_labels_check_all
ENVIRONMENT = var.prefix
LOG_LEVEL = var.log_level
LOG_TYPE = var.log_type
Expand Down
6 changes: 6 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,12 @@ variable "runner_enable_workflow_job_labels_check" {
default = false
}

variable "runner_enable_workflow_job_labels_check_all" {
description = "If set to true all labels in the workflow job must match the GitHub labels (os, architecture and `self-hosted`). When false if __any__ label matches it will trigger the webhook. `runner_enable_workflow_job_labels_check` must be true for this to take effect."
type = bool
default = true
}

variable "runner_ec2_tags" {
description = "Map of tags that will be added to the launch template instance tag specificatons."
type = map(string)
Expand Down

0 comments on commit c5e3c21

Please sign in to comment.