Skip to content

Commit

Permalink
feat: add time zone support for pool schedules (#4063)
Browse files Browse the repository at this point in the history
Fixes #4056

The timezone can be set via the `schedule_expression_timezone` property
via the main module or `multi-runners` module.

Replaces the `aws_cloudwatch_event_rule` in the `pool` module (which
only support UTC) with `aws_scheduler_schedule`, which supports
arbitrary time zones via the `schedule_expression_timezone` attribute.

In this situation AWS EventBridge Scheduler is a drop in replacement for
schedule-based AWS EventBridge Rules ([see AWS blog
post](https://aws.amazon.com/blogs/compute/introducing-amazon-eventbridge-scheduler/)),
including the expression syntax and pricing. The main (internal)
difference is that AWS Scheduler requires the use of an IAM Role,
instead of Lambda permissions.

---------

Co-authored-by: Niek Palm <npalm@users.noreply.github.com>
  • Loading branch information
janslow and npalm authored Aug 16, 2024
1 parent 98b1560 commit b8f9eb4
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 45 deletions.
5 changes: 3 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,9 @@ The pool is introduced in combination with the ephemeral runners and is primaril
```hcl
pool_runner_owner = "my-org" # Org to which the runners are added
pool_config = [{
size = 20 # size of the pool
schedule_expression = "cron(* * * * ? *)" # cron expression to trigger the adjustment of the pool
size = 20 # size of the pool
schedule_expression = "cron(* * * * ? *)" # cron expression to trigger the adjustment of the pool
schedule_expression_timezone = "Australia/Sydney" # optional time zone (defaults to UTC)
}]
```

Expand Down
6 changes: 4 additions & 2 deletions examples/ephemeral/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,10 @@ module "runners" {
# # Example of simple pool usages
# pool_runner_owner = "philips-test-runners"
# pool_config = [{
# size = 3
# schedule_expression = "cron(* * * * ? *)"
# size = 3
# schedule_expression = "cron(0/3 14 * * ? *)" # every 3 minutes between 14:00 and 15:00
# schedule_expression_timezone = "Europe/Amsterdam"

# }]
#
#
Expand Down
7 changes: 4 additions & 3 deletions modules/multi-runner/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ variable "multi_runner_config" {
volume_size = 30
}])
pool_config = optional(list(object({
schedule_expression = string
size = number
schedule_expression = string
schedule_expression_timezone = optional(string)
size = number
})), [])
})

Expand Down Expand Up @@ -177,7 +178,7 @@ variable "multi_runner_config" {
idle_config: "List of time period that can be defined as 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."
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."
block_device_mappings: "The EC2 instance block device configuration. Takes the following keys: `device_name`, `delete_on_termination`, `volume_type`, `volume_size`, `encrypted`, `iops`, `throughput`, `kms_key_id`, `snapshot_id`."
pool_config: "The configuration for updating the pool. The `pool_size` to adjust to by the events triggered by the `schedule_expression`. For example you can configure a cron expression for week days to adjust the pool to 10 and another expression for the weekend to adjust the pool to 1."
pool_config: "The configuration for updating the pool. The `pool_size` to adjust to by the events triggered by the `schedule_expression`. For example you can configure a cron expression for week days to adjust the pool to 10 and another expression for the weekend to adjust the pool to 1. Use `schedule_expression_timezone` to override the schedule time zone (defaults to UTC)."
}
matcherConfig: {
labelMatchers: "The list of list of labels supported by the runner configuration. `[[self-hosted, linux, x64, example]]`"
Expand Down
99 changes: 69 additions & 30 deletions modules/runners/pool/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -118,36 +118,6 @@ data "aws_iam_policy_document" "lambda_assume_role_policy" {
}
}

# per config object one trigger is created to trigger the lambda.
resource "aws_cloudwatch_event_rule" "pool" {
count = length(var.config.pool)

name = "${var.config.prefix}-pool-${count.index}-rule"
schedule_expression = var.config.pool[count.index].schedule_expression
tags = var.config.tags
}

resource "aws_cloudwatch_event_target" "pool" {
count = length(var.config.pool)

input = jsonencode({
poolSize = var.config.pool[count.index].size
})

rule = aws_cloudwatch_event_rule.pool[count.index].name
arn = aws_lambda_function.pool.arn
}

resource "aws_lambda_permission" "pool" {
count = length(var.config.pool)

statement_id = "AllowExecutionFromCloudWatch-${count.index}"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.pool.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.pool[count.index].arn
}

resource "aws_iam_role_policy_attachment" "ami_id_ssm_parameter_read" {
count = var.config.ami_id_ssm_parameter_name != null ? 1 : 0
role = aws_iam_role.pool.name
Expand Down Expand Up @@ -178,3 +148,72 @@ resource "aws_iam_role_policy" "pool_xray" {
policy = data.aws_iam_policy_document.lambda_xray[0].json
role = aws_iam_role.pool.name
}

resource "aws_scheduler_schedule_group" "pool" {
name_prefix = "${var.config.prefix}-pool"

tags = var.config.tags
}

data "aws_iam_policy_document" "scheduler_assume" {
statement {
sid = "ScheduleGroupAssumeRole"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["scheduler.amazonaws.com"]
}

condition {
test = "StringEquals"
variable = "aws:SourceArn"
values = [aws_scheduler_schedule_group.pool.arn]
}
}
}

data "aws_iam_policy_document" "scheduler" {
statement {
sid = "InvokePoolLambda"
actions = ["lambda:InvokeFunction"]
resources = [aws_lambda_function.pool.arn]
}
}

resource "aws_iam_role" "scheduler" {
name_prefix = "${var.config.prefix}-pool"

path = var.config.role_path
permissions_boundary = var.config.role_permissions_boundary

assume_role_policy = data.aws_iam_policy_document.scheduler_assume.json

inline_policy {
name = "terraform"
policy = data.aws_iam_policy_document.scheduler.json
}

tags = var.config.tags
}

resource "aws_scheduler_schedule" "pool" {
for_each = { for i, v in var.config.pool : i => v }

name_prefix = "${var.config.prefix}-pool-${each.key}-rule"
group_name = aws_scheduler_schedule_group.pool.name

flexible_time_window {
mode = "OFF"
}

schedule_expression = each.value.schedule_expression
schedule_expression_timezone = each.value.schedule_expression_timezone

target {
arn = aws_lambda_function.pool.arn
role_arn = aws_iam_role.scheduler.arn
input = jsonencode({
poolSize = each.value.size
})
}
}
5 changes: 3 additions & 2 deletions modules/runners/pool/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,9 @@ variable "config" {
instance_max_spot_price = string
prefix = string
pool = list(object({
schedule_expression = string
size = number
schedule_expression = string
schedule_expression_timezone = string
size = number
}))
role_permissions_boundary = string
kms_key_arn = string
Expand Down
7 changes: 4 additions & 3 deletions modules/runners/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -538,10 +538,11 @@ variable "pool_lambda_reserved_concurrent_executions" {
}

variable "pool_config" {
description = "The configuration for updating the pool. The `pool_size` to adjust to by the events triggered by the `schedule_expression`. For example you can configure a cron expression for week days to adjust the pool to 10 and another expression for the weekend to adjust the pool to 1."
description = "The configuration for updating the pool. The `pool_size` to adjust to by the events triggered by the `schedule_expression`. For example you can configure a cron expression for week days to adjust the pool to 10 and another expression for the weekend to adjust the pool to 1. Use `schedule_expression_timezone ` to override the schedule time zone (defaults to UTC)."
type = list(object({
schedule_expression = string
size = number
schedule_expression = string
schedule_expression_timezone = optional(string)
size = number
}))
default = []
}
Expand Down
7 changes: 4 additions & 3 deletions variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -686,10 +686,11 @@ variable "pool_lambda_reserved_concurrent_executions" {
}

variable "pool_config" {
description = "The configuration for updating the pool. The `pool_size` to adjust to by the events triggered by the `schedule_expression`. For example you can configure a cron expression for weekdays to adjust the pool to 10 and another expression for the weekend to adjust the pool to 1."
description = "The configuration for updating the pool. The `pool_size` to adjust to by the events triggered by the `schedule_expression`. For example you can configure a cron expression for weekdays to adjust the pool to 10 and another expression for the weekend to adjust the pool to 1. Use `schedule_expression_timezone` to override the schedule time zone (defaults to UTC)."
type = list(object({
schedule_expression = string
size = number
schedule_expression = string
schedule_expression_timezone = optional(string)
size = number
}))
default = []
}
Expand Down

0 comments on commit b8f9eb4

Please sign in to comment.