Skip to content
Open
Show file tree
Hide file tree
Changes from all 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: 2 additions & 0 deletions modules/multi-runner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ multi_runner_config = {
| <a name="module_ami_housekeeper"></a> [ami\_housekeeper](#module\_ami\_housekeeper) | ../ami-housekeeper | n/a |
| <a name="module_instance_termination_watcher"></a> [instance\_termination\_watcher](#module\_instance\_termination\_watcher) | ../termination-watcher | n/a |
| <a name="module_runner_binaries"></a> [runner\_binaries](#module\_runner\_binaries) | ../runner-binaries-syncer | n/a |
| <a name="module_runner_configs"></a> [runner\_configs](#module\_runner\_configs) | ../runner-config | n/a |
| <a name="module_runners"></a> [runners](#module\_runners) | ../runners | n/a |
| <a name="module_ssm"></a> [ssm](#module\_ssm) | ../ssm | n/a |
| <a name="module_webhook"></a> [webhook](#module\_webhook) | ../webhook | n/a |
Expand Down Expand Up @@ -270,6 +271,7 @@ multi_runner_config = {
| <a name="output_instance_termination_handler"></a> [instance\_termination\_handler](#output\_instance\_termination\_handler) | n/a |
| <a name="output_instance_termination_watcher"></a> [instance\_termination\_watcher](#output\_instance\_termination\_watcher) | n/a |
| <a name="output_runners_map"></a> [runners\_map](#output\_runners\_map) | n/a |
| <a name="output_runners_map_v2"></a> [runners\_map\_v2](#output\_runners\_map\_v2) | n/a |
| <a name="output_ssm_parameters"></a> [ssm\_parameters](#output\_ssm\_parameters) | n/a |
| <a name="output_webhook"></a> [webhook](#output\_webhook) | n/a |
<!-- END_TF_DOCS -->
12 changes: 12 additions & 0 deletions modules/multi-runner/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ output "runners_map" {
}
}

output "runners_map_v2" {
value = { for runner_key, runner in module.runner_configs : runner_key => {
runner = runner.runner
orchestration_provider = runner.orchestration_provider
scale_up = runner.scale_up
scale_down = runner.scale_down
pool = runner.pool
provider = runner.provider
}
}
}

output "binaries_syncer_map" {
value = { for runner_binary_key, runner_binary in module.runner_binaries : runner_binary_key => {
lambda = runner_binary.lambda
Expand Down
38 changes: 38 additions & 0 deletions modules/multi-runner/runners.experimental.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
module "runner_configs" {
source = "../runner-config"
for_each = {
for runner_key, runner_config in local.effective_config.multi_runner_config :
runner_key => runner_config if local.use_v2_config
}

aws_region = var.aws_region
aws_partition = var.aws_partition
prefix = "${var.prefix}-${each.key}"

tags = merge(
each.value.tags,
{ "ghr:environment" = var.prefix },
)
runner = each.value.runner
github = merge(each.value.github, {
app_parameters = local.github_app_parameters
})
lambda = each.value.lambda
orchestration_provider = {
webhook = each.value.orchestration_provider.webhook == null ? null : {
runner = each.value.orchestration_provider.webhook.runner
github = each.value.orchestration_provider.webhook.github
queue = merge(each.value.orchestration_provider.webhook.queue, {
build = {
arn = aws_sqs_queue.queued_builds[each.key].arn
url = aws_sqs_queue.queued_builds[each.key].url
}
})
lambda = each.value.orchestration_provider.webhook.lambda
job_retry = each.value.orchestration_provider.webhook.job_retry
}
}
ssm = each.value.ssm
observability = each.value.observability
compute_provider = each.value.compute_provider
}
7 changes: 5 additions & 2 deletions modules/multi-runner/runners.tf
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
module "runners" {
source = "../runners"
for_each = local.effective_config.multi_runner_config
source = "../runners"
for_each = {
for runner_key, runner_config in local.effective_config.multi_runner_config :
runner_key => runner_config if !local.use_v2_config
}
aws_region = var.aws_region
aws_partition = var.aws_partition
vpc_id = each.value.compute_provider.aws.ec2.vpc_id
Expand Down
20 changes: 20 additions & 0 deletions modules/multi-runner/tests/config-resolution.tftest.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,16 @@ run "v1_stable_inputs_translate_into_effective_base" {
)
error_message = "Stable v1 inputs must translate into the effective experimental base without leaking v2 globals."
}

assert {
condition = (
keys(module.runners) == ["stable"]
&& length(module.runner_configs) == 0
&& keys(output.runners_map) == ["stable"]
&& length(output.runners_map_v2) == 0
)
error_message = "Stable v1 configurations must route through module.runners and not the experimental runner-config module."
}
}

run "v2_inputs_resolve_lane_over_global" {
Expand Down Expand Up @@ -372,4 +382,14 @@ run "v2_inputs_resolve_lane_over_global" {
)
error_message = "v2 inputs must resolve lane overrides before v2 global defaults."
}

assert {
condition = (
length(module.runners) == 0
&& keys(module.runner_configs) == ["lane"]
&& length(output.runners_map) == 0
&& keys(output.runners_map_v2) == ["lane"]
)
error_message = "Experimental v2 configurations must route through module.runner_configs and skip the legacy runners module."
}
}
2 changes: 1 addition & 1 deletion modules/orchestration-providers/webhook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ The scale-down lifecycle is documented in the [scale-down state diagram](./scale
|------|-------------|------|---------|:--------:|
| <a name="input_aws_partition"></a> [aws\_partition](#input\_aws\_partition) | AWS partition used to construct ARNs. | `string` | `"aws"` | no |
| <a name="input_config"></a> [config](#input\_config) | Provider-owned webhook values supplied from `orchestration_provider.webhook`. The parent resolves inherited input values before calling this module; this provider still resolves the documented JIT, artifact, and tag-precedence fallbacks.<br/><br/>- `runner`: Runner lifecycle, boot timeout, and capacity settings owned by webhook orchestration.<br/>- `runner.boot_time_in_minutes`: Expected runner boot duration used by scale-down and pool controls.<br/>- `runner.ephemeral`: Registers runners in ephemeral mode.<br/>- `runner.jit_config_enabled`: Explicitly enables or disables just-in-time configuration. Null follows `runner.ephemeral`.<br/>- `runner.maximum_count`: Maximum number of runners managed for this runner configuration.<br/>- `github.organization_runners`: Registers runners at organization scope when true; otherwise registration is repository-scoped.<br/>- `queue.build.arn`: ARN of the runner configuration's build queue.<br/>- `queue.build.url`: URL of the runner configuration's build queue.<br/>- `queue.kms_key_id`: Optional KMS key ARN encrypting the build queue. This is independent from the Parameter Store KMS key.<br/>- `queue.tags`: Tags inherited by queue-related provider resources before component-specific overrides.<br/>- `lambda.artifact`: Runner-control artifact shared by scale, pool, and job-retry components. At most one of `zip` or `s3` may be selected; no selection uses the packaged runner archive.<br/>- `lambda.artifact.zip`: Optional local path to the runner-control Lambda archive.<br/>- `lambda.artifact.s3`: Optional S3 object selector in the common `lambda.artifact.s3.bucket`. Wrapper presence must be known during planning and selecting it requires a non-null common bucket.<br/>- `lambda.artifact.s3.key`: Object key of the runner-control Lambda archive.<br/>- `lambda.artifact.s3.object_version`: Optional object version of the runner-control Lambda archive.<br/>- `lambda.scale.up.memory_size`: Memory allocated to the scale-up Lambda in MB.<br/>- `lambda.scale.up.timeout`: Scale-up Lambda timeout in seconds.<br/>- `lambda.scale.up.reserved_concurrent_executions`: Reserved concurrency for scale-up. Use `-1` for unreserved concurrency.<br/>- `lambda.scale.up.job_queued_check_enabled`: Enables queued-job verification before scaling. Null follows the resolved runner mode.<br/>- `lambda.scale.up.event_source_mapping.batch_size`: Maximum build-queue records delivered per scale-up invocation.<br/>- `lambda.scale.up.event_source_mapping.maximum_batching_window_in_seconds`: Maximum batching window for build-queue records.<br/>- `lambda.scale.up.tags`: Tags applied within scale-up resource scopes after common provider tags.<br/>- `lambda.scale.down.memory_size`: Memory allocated to the scale-down Lambda in MB.<br/>- `lambda.scale.down.timeout`: Scale-down Lambda timeout in seconds.<br/>- `lambda.scale.down.schedule_expression`: EventBridge schedule expression that invokes scale-down.<br/>- `lambda.scale.down.minimum_running_time_in_minutes`: Optional minimum runner age before scale-down may terminate it. Null selects the operating-system default.<br/>- `lambda.scale.down.idle_config`: Time-based desired idle-runner configurations.<br/>- `lambda.scale.down.idle_config[].cron`: Cron expression identifying when the idle configuration applies.<br/>- `lambda.scale.down.idle_config[].timeZone`: IANA time zone used to evaluate the cron expression.<br/>- `lambda.scale.down.idle_config[].idleCount`: Number of idle runners retained during the matching period.<br/>- `lambda.scale.down.idle_config[].evictionStrategy`: Selection strategy used when excess idle runners are removed.<br/>- `lambda.scale.down.tags`: Tags applied within scale-down resource scopes after common provider tags.<br/>- `lambda.pool.memory_size`: Memory allocated to the pool Lambda in MB.<br/>- `lambda.pool.timeout`: Pool Lambda timeout in seconds.<br/>- `lambda.pool.reserved_concurrent_executions`: Reserved concurrency for the pool Lambda. Use `-1` for unreserved concurrency.<br/>- `lambda.pool.config`: Scheduled target pool sizes. An empty list disables the pool component.<br/>- `lambda.pool.config[].schedule_expression`: Scheduler expression that activates the target size.<br/>- `lambda.pool.config[].schedule_expression_timezone`: Optional IANA time zone used to evaluate the schedule.<br/>- `lambda.pool.config[].size`: Desired number of runners for the schedule.<br/>- `lambda.pool.include_busy_runners`: Includes busy runners when reconciling scheduled pool capacity.<br/>- `lambda.pool.runner_owner`: Optional GitHub organization or repository owner used for pooled runners.<br/>- `lambda.pool.tags`: Tags applied within pool resource scopes after common provider tags.<br/>- `job_retry.enabled`: Creates the retry queue, Lambda function, event-source mapping, and related IAM resources.<br/>- `job_retry.delay_in_seconds`: Initial delay before a queued-job retry check.<br/>- `job_retry.delay_backoff`: Multiplier applied to the delay after each unsuccessful check.<br/>- `job_retry.max_attempts`: Maximum retry-check attempts before the message is no longer republished.<br/>- `job_retry.tags`: Tags applied within job-retry resource scopes after common provider tags.<br/>- `job_retry.lambda.memory_size`: Memory allocated to the job-retry Lambda in MB.<br/>- `job_retry.lambda.reserved_concurrent_executions`: Reserved concurrency for job retry. Use `-1` for unreserved concurrency.<br/>- `job_retry.lambda.timeout`: Job-retry Lambda timeout in seconds and visibility timeout for its retry queue. | <pre>object({<br/> runner = object({<br/> boot_time_in_minutes = number<br/> ephemeral = bool<br/> jit_config_enabled = optional(bool, null)<br/> maximum_count = number<br/> })<br/> github = object({<br/> organization_runners = bool<br/> })<br/> queue = object({<br/> build = object({<br/> arn = string<br/> url = string<br/> })<br/> kms_key_id = optional(string, null)<br/> tags = optional(map(string), {})<br/> })<br/> lambda = object({<br/> artifact = object({<br/> zip = optional(string, null)<br/> s3 = optional(object({<br/> key = string<br/> object_version = optional(string, null)<br/> }), null)<br/> })<br/> scale = object({<br/> up = object({<br/> memory_size = number<br/> timeout = number<br/> reserved_concurrent_executions = number<br/> job_queued_check_enabled = optional(bool, null)<br/> event_source_mapping = object({<br/> batch_size = number<br/> maximum_batching_window_in_seconds = number<br/> })<br/> tags = optional(map(string), {})<br/> })<br/> down = object({<br/> memory_size = number<br/> timeout = number<br/> schedule_expression = string<br/> minimum_running_time_in_minutes = optional(number, null)<br/> idle_config = list(object({<br/> cron = string<br/> timeZone = string<br/> idleCount = number<br/> evictionStrategy = string<br/> }))<br/> tags = optional(map(string), {})<br/> })<br/> })<br/> pool = object({<br/> memory_size = number<br/> timeout = number<br/> reserved_concurrent_executions = number<br/> config = list(object({<br/> schedule_expression = string<br/> schedule_expression_timezone = optional(string)<br/> size = number<br/> }))<br/> include_busy_runners = bool<br/> runner_owner = optional(string, null)<br/> tags = optional(map(string), {})<br/> })<br/> })<br/> job_retry = object({<br/> enabled = bool<br/> delay_in_seconds = number<br/> delay_backoff = number<br/> max_attempts = number<br/> tags = optional(map(string), {})<br/> lambda = object({<br/> memory_size = number<br/> reserved_concurrent_executions = number<br/> timeout = number<br/> })<br/> })<br/> })</pre> | n/a | yes |
| <a name="input_github"></a> [github](#input\_github) | Common GitHub API client and GitHub App Parameter Store references. | <pre>object({<br/> app_parameters = object({<br/> key_base64 = list(map(string))<br/> id = list(map(string))<br/> installation_id = list(object({ name = string, arn = string }))<br/> })<br/> enterprise_server = object({<br/> url = optional(string, null)<br/> ssl_verify = bool<br/> })<br/> user_agent = optional(string, null)<br/> })</pre> | n/a | yes |
| <a name="input_github"></a> [github](#input\_github) | Common GitHub API client and GitHub App Parameter Store references. | <pre>object({<br/> app_parameters = object({<br/> key_base64 = list(map(string))<br/> id = list(map(string))<br/> installation_id = optional(list(object({ name = string, arn = string })), [null])<br/> })<br/> enterprise_server = object({<br/> url = optional(string, null)<br/> ssl_verify = bool<br/> })<br/> user_agent = optional(string, null)<br/> })</pre> | n/a | yes |
| <a name="input_lambda"></a> [lambda](#input\_lambda) | Common Lambda substrate. Only the shared artifact bucket crosses this boundary; the webhook provider owns its archive key, version, and local zip selection. | <pre>object({<br/> artifact = object({<br/> s3 = object({<br/> bucket = optional(string, null)<br/> })<br/> })<br/> runtime = string<br/> architecture = string<br/> subnet_ids = list(string)<br/> security_group_ids = list(string)<br/> tags = optional(map(string), {})<br/> role = object({<br/> path = string<br/> permissions_boundary = optional(string, null)<br/> principals = optional(list(object({<br/> type = string<br/> identifiers = list(string)<br/> })), [])<br/> })<br/> })</pre> | n/a | yes |
| <a name="input_observability"></a> [observability](#input\_observability) | Common logging, tracing, and metrics configuration consumed by webhook controls. | <pre>object({<br/> logs = object({<br/> level = string<br/> retention_in_days = number<br/> kms_key_id = optional(string, null)<br/> class = string<br/> tags = optional(map(string), {})<br/> })<br/> tracing = object({<br/> mode = optional(string, null)<br/> capture_http_requests = bool<br/> capture_error = bool<br/> })<br/> metrics = object({<br/> enabled = bool<br/> namespace = string<br/> metric = object({<br/> github_app_rate_limit = object({<br/> enabled = bool<br/> })<br/> job_retry = object({<br/> enabled = bool<br/> })<br/> })<br/> })<br/> })</pre> | n/a | yes |
| <a name="input_prefix"></a> [prefix](#input\_prefix) | Prefix used to identify resources created for this webhook orchestration provider. | `string` | n/a | yes |
Expand Down
Loading