Skip to content
This repository was archived by the owner on Jan 16, 2025. It is now read-only.

Commit

Permalink
feat: Natively support runner pre/post job hooks (#4263)
Browse files Browse the repository at this point in the history
Adds two optional string arguments to the module
- `runner_hook_job_started`
- `runner_hook_job_completed`

If not empty, registers the hook in `user-data.sh`

Ref:
https://github.com/actions/runner/blob/main/docs/adrs/1751-runner-job-hooks.md

Closes:
https://github.com/philips-labs/terraform-aws-github-runner/issues/4260
Discussion:
https://github.com/philips-labs/terraform-aws-github-runner/issues/3854

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Niek Palm <[email protected]>
Co-authored-by: Niek Palm <[email protected]>
  • Loading branch information
4 people authored Dec 20, 2024
1 parent 401a373 commit 259a852
Show file tree
Hide file tree
Showing 12 changed files with 81 additions and 9 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,8 @@ Talk to the forestkeepers in the `runners-channel` on Slack.
| <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_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. | `list(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_hook_job_completed"></a> [runner\_hook\_job\_completed](#input\_runner\_hook\_job\_completed) | Script to be ran in the runner environment at the end of every job | `string` | `""` | no |
| <a name="input_runner_hook_job_started"></a> [runner\_hook\_job\_started](#input\_runner\_hook\_job\_started) | Script to be ran in the runner environment at the beginning of every job | `string` | `""` | 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 |
| <a name="input_runner_log_files"></a> [runner\_log\_files](#input\_runner\_log\_files) | (optional) Replaces the module default cloudwatch log config. See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-Configuration-File-Details.html for details. | <pre>list(object({<br/> log_group_name = string<br/> prefix_log_group = bool<br/> file_path = string<br/> log_stream_name = string<br/> }))</pre> | `null` | no |
| <a name="input_runner_metadata_options"></a> [runner\_metadata\_options](#input\_runner\_metadata\_options) | Metadata options for the ec2 runner instances. By default, the module uses metadata tags for bootstrapping the runner, only disable `instance_metadata_tags` when using custom scripts for starting the runner. | `map(any)` | <pre>{<br/> "http_endpoint": "enabled",<br/> "http_put_response_hop_limit": 1,<br/> "http_tokens": "required",<br/> "instance_metadata_tags": "enabled"<br/>}</pre> | no |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,7 @@ runner_config:
runners_maximum_count: 1
delay_webhook_event: 0
scale_down_schedule_expression: cron(* * * * ? *)
runner_hook_job_started: |
echo "Running pre job hook as \$(whoami)"
runner_hook_job_completed: |
echo "Running post job hook as \$(whoami)"
14 changes: 14 additions & 0 deletions examples/multi-runner/templates/user-data.sh
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,18 @@ ${post_install}

cd /opt/actions-runner

%{ if hook_job_started != "" }
cat > /opt/actions-runner/hook_job_started.sh << EOF
${hook_job_started}
EOF
echo ACTIONS_RUNNER_HOOK_JOB_STARTED=/opt/actions-runner/hook_job_started.sh | tee -a /opt/actions-runner/.env
%{ endif }

%{ if hook_job_completed != "" }
cat > /opt/actions-runner/hook_job_completed.sh << EOF
${hook_job_completed}
EOF
echo ACTIONS_RUNNER_HOOK_JOB_COMPLETED=/opt/actions-runner/hook_job_completed.sh | tee -a /opt/actions-runner/.env
%{ endif }

${start_runner}
2 changes: 2 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ module "runners" {
userdata_content = var.userdata_content
userdata_pre_install = var.userdata_pre_install
userdata_post_install = var.userdata_post_install
runner_hook_job_started = var.runner_hook_job_started
runner_hook_job_completed = var.runner_hook_job_completed
key_name = var.key_name
runner_ec2_tags = var.runner_ec2_tags

Expand Down
2 changes: 1 addition & 1 deletion modules/multi-runner/README.md

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions modules/multi-runner/runners.tf
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,15 @@ module "runners" {
role_path = var.role_path
role_permissions_boundary = var.role_permissions_boundary

enable_userdata = each.value.runner_config.enable_userdata
userdata_template = each.value.runner_config.userdata_template
userdata_content = each.value.runner_config.userdata_content
userdata_pre_install = each.value.runner_config.userdata_pre_install
userdata_post_install = each.value.runner_config.userdata_post_install
key_name = var.key_name
runner_ec2_tags = each.value.runner_config.runner_ec2_tags
enable_userdata = each.value.runner_config.enable_userdata
userdata_template = each.value.runner_config.userdata_template
userdata_content = each.value.runner_config.userdata_content
userdata_pre_install = each.value.runner_config.userdata_pre_install
userdata_post_install = each.value.runner_config.userdata_post_install
runner_hook_job_started = each.value.runner_config.runner_hook_job_started
runner_hook_job_completed = each.value.runner_config.runner_hook_job_completed
key_name = var.key_name
runner_ec2_tags = each.value.runner_config.runner_ec2_tags

create_service_linked_role_spot = each.value.runner_config.create_service_linked_role_spot

Expand Down
4 changes: 4 additions & 0 deletions modules/multi-runner/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ variable "multi_runner_config" {
cloudwatch_config = optional(string, null)
userdata_pre_install = optional(string, "")
userdata_post_install = optional(string, "")
runner_hook_job_started = optional(string, "")
runner_hook_job_completed = optional(string, "")
runner_ec2_tags = optional(map(string), {})
runner_iam_role_managed_policy_arns = optional(list(string), [])
vpc_id = optional(string, null)
Expand Down Expand Up @@ -180,6 +182,8 @@ variable "multi_runner_config" {
cloudwatch_config: "(optional) Replaces the module default cloudwatch log config. See https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch-Agent-Configuration-File-Details.html for details."
userdata_pre_install: "Script to be ran before the GitHub Actions runner is installed on the EC2 instances"
userdata_post_install: "Script to be ran after the GitHub Actions runner is installed on the EC2 instances"
runner_hook_job_started: "Script to be ran in the runner environment at the beginning of every job"
runner_hook_job_completed: "Script to be ran in the runner environment at the end of every job"
runner_ec2_tags: "Map of tags that will be added to the launch template instance tag specifications."
runner_iam_role_managed_policy_arns: "Attach AWS or customer-managed IAM policies (by ARN) to the runner IAM role"
vpc_id: "The VPC for security groups of the action runners. If not set uses the value of `var.vpc_id`."
Expand Down
2 changes: 2 additions & 0 deletions modules/runners/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ yarn run dist
| <a name="input_runner_disable_default_labels"></a> [runner\_disable\_default\_labels](#input\_runner\_disable\_default\_labels) | Disable default labels for the runners (os, architecture and `self-hosted`). If enabled, the runner will only have the extra labels provided in `runner_extra_labels`. | `bool` | `false` | no |
| <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 specifications. | `map(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_hook_job_completed"></a> [runner\_hook\_job\_completed](#input\_runner\_hook\_job\_completed) | Script to be ran in the runner environment at the end of every job | `string` | `""` | no |
| <a name="input_runner_hook_job_started"></a> [runner\_hook\_job\_started](#input\_runner\_hook\_job\_started) | Script to be ran in the runner environment at the beginning of every job | `string` | `""` | 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 |
| <a name="input_runner_labels"></a> [runner\_labels](#input\_runner\_labels) | All the labels for the runners (GitHub) including the default one's(e.g: self-hosted, linux, x64, label1, label2). Separate each label by a comma | `list(string)` | n/a | yes |
| <a name="input_runner_log_files"></a> [runner\_log\_files](#input\_runner\_log\_files) | (optional) List of logfiles to send to CloudWatch, will only be used if `enable_cloudwatch_agent` is set to true. Object description: `log_group_name`: Name of the log group, `prefix_log_group`: If true, the log group name will be prefixed with `/github-self-hosted-runners/<var.prefix>`, `file_path`: path to the log file, `log_stream_name`: name of the log stream. | <pre>list(object({<br/> log_group_name = string<br/> prefix_log_group = bool<br/> file_path = string<br/> log_stream_name = string<br/> }))</pre> | `null` | no |
Expand Down
4 changes: 3 additions & 1 deletion modules/runners/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ locals {
S3_LOCATION_RUNNER_DISTRIBUTION = local.s3_location_runner_distribution
RUNNER_ARCHITECTURE = var.runner_architecture
})
post_install = var.userdata_post_install
post_install = var.userdata_post_install
hook_job_started = var.runner_hook_job_started
hook_job_completed = var.runner_hook_job_completed
start_runner = templatefile(local.userdata_start_runner[var.runner_os], {
metadata_tags = var.metadata_options != null ? var.metadata_options.instance_metadata_tags : "enabled"
})
Expand Down
16 changes: 16 additions & 0 deletions modules/runners/templates/user-data.sh
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,20 @@ ${install_runner}

${post_install}

# Register runner job hooks
# Ref: https://github.com/actions/runner/blob/main/docs/adrs/1751-runner-job-hooks.md
%{ if hook_job_started != "" }
cat > /opt/actions-runner/hook_job_started.sh << EOF
${hook_job_started}
EOF
echo ACTIONS_RUNNER_HOOK_JOB_STARTED=/opt/actions-runner/hook_job_started.sh | tee -a /opt/actions-runner/.env
%{ endif }

%{ if hook_job_completed != "" }
cat > /opt/actions-runner/hook_job_completed.sh << EOF
${hook_job_completed}
EOF
echo ACTIONS_RUNNER_HOOK_JOB_COMPLETED=/opt/actions-runner/hook_job_completed.sh | tee -a /opt/actions-runner/.env
%{ endif }

${start_runner}
12 changes: 12 additions & 0 deletions modules/runners/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,18 @@ variable "userdata_post_install" {
default = ""
}

variable "runner_hook_job_started" {
description = "Script to be ran in the runner environment at the beginning of every job"
type = string
default = ""
}

variable "runner_hook_job_completed" {
description = "Script to be ran in the runner environment at the end of every job"
type = string
default = ""
}

variable "sqs_build_queue" {
description = "SQS queue to consume accepted build events."
type = object({
Expand Down
12 changes: 12 additions & 0 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,18 @@ variable "userdata_post_install" {
description = "Script to be ran after the GitHub Actions runner is installed on the EC2 instances"
}

variable "runner_hook_job_started" {
type = string
default = ""
description = "Script to be ran in the runner environment at the beginning of every job"
}

variable "runner_hook_job_completed" {
type = string
default = ""
description = "Script to be ran in the runner environment at the end of every job"
}

variable "idle_config" {
description = "List of time periods, defined as a cron expression, to keep a minimum amount of runners active instead of scaling down to 0. By defining this list you can ensure that in time periods that match the cron expression within 5 seconds a runner is kept idle."
type = list(object({
Expand Down

0 comments on commit 259a852

Please sign in to comment.