From 7036f1bad8183dd15e1f53c7a5c7c37502e687ae Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Thu, 13 Aug 2026 14:19:06 +0200 Subject: [PATCH 1/2] feat(microvm): add Terraform compute provider lane --- .github/workflows/terraform.yml | 4 + .../microvm/control-plane.tf | 110 ++++++++++ modules/compute-providers/microvm/outputs.tf | 23 ++ .../microvm/provider-contract.tf | 34 +++ .../microvm/tests/provider.tftest.hcl | 194 +++++++++++++++++ .../microvm/trust-policy/assume-role.tf | 21 ++ .../microvm/trust-policy/outputs.tf | 4 + .../tests/trust-policy.tftest.hcl | 59 ++++++ .../microvm/trust-policy/variables.tf | 10 + .../microvm/trust-policy/versions.tf | 10 + .../compute-providers/microvm/validations.tf | 48 +++++ .../compute-providers/microvm/variables.tf | 200 ++++++++++++++++++ modules/compute-providers/microvm/versions.tf | 10 + .../tests/provider-routing.tftest.hcl | 77 +++++++ .../multi-runner/variables.experimental.tf | 53 ++++- modules/runner-stack/compute-provider.tf | 6 +- modules/runner-stack/microvm.tf | 27 +++ modules/runner-stack/tests/pool.tftest.hcl | 79 +++++++ .../variables.compute-provider.tf | 53 ++++- modules/webhook/variables.tf | 2 +- 20 files changed, 1019 insertions(+), 5 deletions(-) create mode 100644 modules/compute-providers/microvm/control-plane.tf create mode 100644 modules/compute-providers/microvm/outputs.tf create mode 100644 modules/compute-providers/microvm/provider-contract.tf create mode 100644 modules/compute-providers/microvm/tests/provider.tftest.hcl create mode 100644 modules/compute-providers/microvm/trust-policy/assume-role.tf create mode 100644 modules/compute-providers/microvm/trust-policy/outputs.tf create mode 100644 modules/compute-providers/microvm/trust-policy/tests/trust-policy.tftest.hcl create mode 100644 modules/compute-providers/microvm/trust-policy/variables.tf create mode 100644 modules/compute-providers/microvm/trust-policy/versions.tf create mode 100644 modules/compute-providers/microvm/validations.tf create mode 100644 modules/compute-providers/microvm/variables.tf create mode 100644 modules/compute-providers/microvm/versions.tf create mode 100644 modules/runner-stack/microvm.tf diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml index 4a6def3312..7bfbf2930a 100644 --- a/.github/workflows/terraform.yml +++ b/.github/workflows/terraform.yml @@ -87,6 +87,8 @@ jobs: "multi-runner", "compute-providers/ec2", "compute-providers/ec2/trust-policy", + "compute-providers/microvm", + "compute-providers/microvm/trust-policy", "runner-binaries-syncer", "runner-stack", "runner-stack/job-retry", @@ -228,6 +230,8 @@ jobs: - modules/runner-stack/ssm-housekeeper - modules/compute-providers/ec2 - modules/compute-providers/ec2/trust-policy + - modules/compute-providers/microvm + - modules/compute-providers/microvm/trust-policy defaults: run: working-directory: ${{ matrix.module }} diff --git a/modules/compute-providers/microvm/control-plane.tf b/modules/compute-providers/microvm/control-plane.tf new file mode 100644 index 0000000000..503ee58d3d --- /dev/null +++ b/modules/compute-providers/microvm/control-plane.tf @@ -0,0 +1,110 @@ +data "aws_iam_policy_document" "scale_up" { + statement { + effect = "Allow" + actions = local.scale_up_actions + resources = var.config.iam.resource_arns + } + + statement { + effect = "Allow" + actions = ["iam:PassRole"] + resources = [local.execution_role_arn] + } +} + +data "aws_iam_policy_document" "scale_down" { + statement { + effect = "Allow" + actions = local.scale_down_actions + resources = var.config.iam.resource_arns + } +} + +locals { + default_scale_up_actions = [ + "lambdamicrovms:CreateMicrovmAuthToken", + "lambdamicrovms:GetMicrovm", + "lambdamicrovms:ListMicrovms", + "lambdamicrovms:RunMicrovm", + "lambdamicrovms:TagResource", + ] + + default_scale_down_actions = [ + "lambdamicrovms:GetMicrovm", + "lambdamicrovms:ListMicrovms", + "lambdamicrovms:ListTags", + "lambdamicrovms:TagResource", + "lambdamicrovms:TerminateMicrovm", + "lambdamicrovms:UntagResource", + ] + + scale_up_actions = coalesce(var.config.iam.actions.scale_up, local.default_scale_up_actions) + scale_down_actions = coalesce(var.config.iam.actions.scale_down, local.default_scale_down_actions) + + execution_role_arn = coalesce(try(var.config.execution_role.arn, null), var.runner.iam.role.arn) + + microvm_tags = merge( + var.tags, + var.config.tags, + { + "ghr:Application" = "github-action-runner" + "ghr:environment" = var.prefix + "ghr:runner_name_prefix" = var.runner.name_prefix + }, + ) + + microvm_idle_policy = var.config.idle_policy == null ? null : { + maxIdleDurationSeconds = var.config.idle_policy.max_idle_duration_seconds + suspendedDurationSeconds = var.config.idle_policy.suspended_duration_seconds + autoResumeEnabled = var.config.idle_policy.auto_resume_enabled + } + + microvm_logging = var.config.logging == null ? null : ( + var.config.logging.disabled ? { + disabled = {} + } : { + cloudWatch = { + logGroup = try(var.config.logging.cloud_watch.log_group, null) + logStream = try(var.config.logging.cloud_watch.log_stream, null) + } + } + ) + + microvm_run_config = { + imageIdentifier = var.config.image_identifier + imageVersion = var.config.image_version + executionRoleArn = local.execution_role_arn + egressNetworkConnectors = var.config.egress_network_connectors + idlePolicy = local.microvm_idle_policy + logging = local.microvm_logging + runHookPayload = var.config.run_hook_payload + maximumDurationInSeconds = var.config.maximum_duration_in_seconds + tags = local.microvm_tags + } + + create_environment_variables = merge(var.config.environment_variables, { + MICROVM_AWS_PARTITION = var.aws_partition + MICROVM_AWS_REGION = var.aws_region + MICROVM_EGRESS_NETWORK_CONNECTORS = jsonencode(var.config.egress_network_connectors) + MICROVM_EXECUTION_ROLE_ARN = local.execution_role_arn + MICROVM_IMAGE_IDENTIFIER = var.config.image_identifier + MICROVM_IMAGE_VERSION = var.config.image_version == null ? "" : var.config.image_version + MICROVM_RUN_CONFIG = jsonencode(local.microvm_run_config) + MICROVM_TAGS = jsonencode(local.microvm_tags) + }) + + scale_up_environment_variables = local.create_environment_variables + + scale_down_environment_variables = merge(var.config.environment_variables, { + MICROVM_AWS_PARTITION = var.aws_partition + MICROVM_AWS_REGION = var.aws_region + MICROVM_IMAGE_IDENTIFIER = var.config.image_identifier + MICROVM_IMAGE_VERSION = var.config.image_version == null ? "" : var.config.image_version + MICROVM_TAGS = jsonencode(local.microvm_tags) + RUNNER_BOOT_TIME_IN_MINUTES = var.runner.boot_time_in_minutes + }) + + pool_environment_variables = merge(local.create_environment_variables, { + RUNNER_BOOT_TIME_IN_MINUTES = var.runner.boot_time_in_minutes + }) +} diff --git a/modules/compute-providers/microvm/outputs.tf b/modules/compute-providers/microvm/outputs.tf new file mode 100644 index 0000000000..fd2f8c3bb1 --- /dev/null +++ b/modules/compute-providers/microvm/outputs.tf @@ -0,0 +1,23 @@ +output "environment_variables" { + description = "Provider-specific Lambda environment variable fragments consumed by runner-stack." + value = local.provider_environment_variables +} + +output "policies" { + description = "Provider-specific IAM policy fragments consumed by runner-stack." + value = local.provider_policies +} + +output "resources" { + description = "Provider-specific MicroVM resources exposed by runner-stack." + value = local.provider_resources +} + +output "provider" { + description = "Nested Lambda MicroVM compute-provider contract consumed by runner-stack." + value = { + environment_variables = local.provider_environment_variables + policies = local.provider_policies + resources = local.provider_resources + } +} diff --git a/modules/compute-providers/microvm/provider-contract.tf b/modules/compute-providers/microvm/provider-contract.tf new file mode 100644 index 0000000000..0aff22a31a --- /dev/null +++ b/modules/compute-providers/microvm/provider-contract.tf @@ -0,0 +1,34 @@ +locals { + provider_environment_variables = { + scale_up = local.scale_up_environment_variables + scale_down = local.scale_down_environment_variables + pool = local.pool_environment_variables + } + + provider_policies = { + runner = { + inline_policies = {} + managed_policy_arns = var.runner.iam.managed_policy_arns + } + scale_up = { + iam_policy_json = data.aws_iam_policy_document.scale_up.json + additional_iam_policy_json = var.config.iam.additional_policy_json.scale_up + managed_policy_enabled = var.config.iam.managed_policy_arns.scale_up != null + managed_policy_arn = var.config.iam.managed_policy_arns.scale_up + } + scale_down = { + iam_policy_json = data.aws_iam_policy_document.scale_down.json + } + pool = { + iam_policy_json = data.aws_iam_policy_document.scale_up.json + managed_policy_enabled = var.config.iam.managed_policy_arns.pool != null + managed_policy_arn = var.config.iam.managed_policy_arns.pool + } + } + + provider_resources = { + image_identifier = var.config.image_identifier + image_version = var.config.image_version + execution_role_arn = local.execution_role_arn + } +} diff --git a/modules/compute-providers/microvm/tests/provider.tftest.hcl b/modules/compute-providers/microvm/tests/provider.tftest.hcl new file mode 100644 index 0000000000..9c4d3988dd --- /dev/null +++ b/modules/compute-providers/microvm/tests/provider.tftest.hcl @@ -0,0 +1,194 @@ +mock_provider "aws" { + mock_data "aws_iam_policy_document" { + defaults = { + json = "{}" + } + } +} + +variables { + aws_region = "eu-west-1" + prefix = "microvm-test" + + tags = { + Module = "runner" + } + + config = { + image_identifier = "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner" + image_version = "3" + egress_network_connectors = [ + "egress-connector" + ] + idle_policy = { + max_idle_duration_seconds = 300 + suspended_duration_seconds = 900 + auto_resume_enabled = true + } + logging = { + cloud_watch = { + log_group = "/aws/lambdamicrovms/runner" + log_stream = "runtime" + } + } + run_hook_payload = "{\"runner\":\"test\"}" + maximum_duration_in_seconds = 3600 + environment_variables = { + MICROVM_CLUSTER = "runner-cluster" + } + tags = { + Provider = "microvm" + } + } + + runner = { + boot_time_in_minutes = 7 + name_prefix = "microvm-" + iam = { + role = { + arn = "arn:aws:iam::123456789012:role/microvm-test-runner" + name = "microvm-test-runner" + } + managed_policy_arns = { + readonly = "arn:aws:iam::aws:policy/ReadOnlyAccess" + } + } + } + + ssm = { + paths = { + root = "/github-action-runners" + tokens = "tokens" + config = "config" + } + } +} + +run "exposes_microvm_control_plane_contract" { + command = plan + + assert { + condition = toset(keys(output.provider)) == toset(["environment_variables", "policies", "resources"]) + error_message = "The MicroVM provider contract must expose only integration and resource data." + } + + assert { + condition = ( + output.provider.environment_variables.scale_up["MICROVM_CLUSTER"] == "runner-cluster" + && output.provider.environment_variables.scale_up["MICROVM_IMAGE_IDENTIFIER"] == "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner" + && output.provider.environment_variables.scale_up["MICROVM_IMAGE_VERSION"] == "3" + && output.provider.environment_variables.scale_up["MICROVM_EXECUTION_ROLE_ARN"] == "arn:aws:iam::123456789012:role/microvm-test-runner" + && output.provider.environment_variables.scale_down["RUNNER_BOOT_TIME_IN_MINUTES"] == 7 + && output.provider.environment_variables.pool["RUNNER_BOOT_TIME_IN_MINUTES"] == 7 + ) + error_message = "The MicroVM provider must expose scale-up, scale-down, and pool environment fragments." + } + + assert { + condition = ( + jsondecode(output.provider.environment_variables.scale_up["MICROVM_RUN_CONFIG"]).imageIdentifier == "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner" + && jsondecode(output.provider.environment_variables.scale_up["MICROVM_RUN_CONFIG"]).executionRoleArn == "arn:aws:iam::123456789012:role/microvm-test-runner" + && jsondecode(output.provider.environment_variables.scale_up["MICROVM_RUN_CONFIG"]).idlePolicy.maxIdleDurationSeconds == 300 + && jsondecode(output.provider.environment_variables.scale_up["MICROVM_RUN_CONFIG"]).logging.cloudWatch.logGroup == "/aws/lambdamicrovms/runner" + && jsondecode(output.provider.environment_variables.scale_up["MICROVM_TAGS"]).Provider == "microvm" + && jsondecode(output.provider.environment_variables.scale_up["MICROVM_TAGS"])["ghr:environment"] == "microvm-test" + ) + error_message = "The MicroVM provider must encode the RunMicrovm request and protected runner tags." + } + + assert { + condition = ( + contains(data.aws_iam_policy_document.scale_up.statement[0].actions, "lambdamicrovms:RunMicrovm") + && contains(data.aws_iam_policy_document.scale_up.statement[0].actions, "lambdamicrovms:CreateMicrovmAuthToken") + && data.aws_iam_policy_document.scale_up.statement[1].actions == toset(["iam:PassRole"]) + && data.aws_iam_policy_document.scale_up.statement[1].resources == toset(["arn:aws:iam::123456789012:role/microvm-test-runner"]) + && contains(data.aws_iam_policy_document.scale_down.statement[0].actions, "lambdamicrovms:TerminateMicrovm") + ) + error_message = "The MicroVM provider must own MicroVM scale-up, scale-down, and PassRole permissions." + } + + assert { + condition = ( + toset(keys(output.provider.policies)) == toset(["runner", "scale_up", "scale_down", "pool"]) + && length(output.provider.policies.runner.inline_policies) == 0 + && output.provider.policies.runner.managed_policy_arns["readonly"] == "arn:aws:iam::aws:policy/ReadOnlyAccess" + && !output.provider.policies.scale_up.managed_policy_enabled + && !output.provider.policies.pool.managed_policy_enabled + ) + error_message = "The MicroVM provider must return policy fragments grouped by common component." + } + + assert { + condition = output.provider.resources == { + image_identifier = "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner" + image_version = "3" + execution_role_arn = "arn:aws:iam::123456789012:role/microvm-test-runner" + } + error_message = "The MicroVM provider must expose its selected image and execution role as provider resources." + } +} + +run "accepts_external_execution_role_and_policy_overrides" { + command = plan + + variables { + config = { + image_identifier = "runner-image" + execution_role = { + arn = "arn:aws:iam::123456789012:role/external-microvm-execution" + } + logging = { + disabled = true + } + iam = { + resource_arns = ["arn:aws:lambdamicrovms:eu-west-1:123456789012:microvm/*"] + actions = { + scale_up = ["lambdamicrovms:RunMicrovm"] + scale_down = ["lambdamicrovms:TerminateMicrovm"] + } + additional_policy_json = { + scale_up = "{\"Version\":\"2012-10-17\",\"Statement\":[]}" + } + managed_policy_arns = { + scale_up = "arn:aws:iam::123456789012:policy/microvm-scale-up" + pool = "arn:aws:iam::123456789012:policy/microvm-pool" + } + } + } + } + + assert { + condition = ( + output.provider.environment_variables.scale_up["MICROVM_EXECUTION_ROLE_ARN"] == "arn:aws:iam::123456789012:role/external-microvm-execution" + && jsondecode(output.provider.environment_variables.scale_up["MICROVM_RUN_CONFIG"]).logging.disabled == {} + && data.aws_iam_policy_document.scale_up.statement[0].actions == toset(["lambdamicrovms:RunMicrovm"]) + && data.aws_iam_policy_document.scale_up.statement[0].resources == toset(["arn:aws:lambdamicrovms:eu-west-1:123456789012:microvm/*"]) + && data.aws_iam_policy_document.scale_up.statement[1].resources == toset(["arn:aws:iam::123456789012:role/external-microvm-execution"]) + && data.aws_iam_policy_document.scale_down.statement[0].actions == toset(["lambdamicrovms:TerminateMicrovm"]) + ) + error_message = "External execution role and action/resource overrides must reach the MicroVM provider contract." + } + + assert { + condition = ( + output.provider.policies.scale_up.additional_iam_policy_json == "{\"Version\":\"2012-10-17\",\"Statement\":[]}" + && output.provider.policies.scale_up.managed_policy_enabled + && output.provider.policies.scale_up.managed_policy_arn == "arn:aws:iam::123456789012:policy/microvm-scale-up" + && output.provider.policies.pool.managed_policy_enabled + && output.provider.policies.pool.managed_policy_arn == "arn:aws:iam::123456789012:policy/microvm-pool" + ) + error_message = "Optional MicroVM policy attachments must stay controlled by object presence." + } +} + +run "rejects_empty_image_identifier" { + command = plan + + variables { + config = { + image_identifier = " " + } + } + + expect_failures = [terraform_data.validate_config] +} diff --git a/modules/compute-providers/microvm/trust-policy/assume-role.tf b/modules/compute-providers/microvm/trust-policy/assume-role.tf new file mode 100644 index 0000000000..3654bce8bf --- /dev/null +++ b/modules/compute-providers/microvm/trust-policy/assume-role.tf @@ -0,0 +1,21 @@ +data "aws_iam_policy_document" "default" { + statement { + effect = "Allow" + actions = [ + "sts:AssumeRole", + "sts:TagSession", + ] + + principals { + type = "Service" + identifiers = ["lambda.amazonaws.com"] + } + } +} + +data "aws_iam_policy_document" "assume_role" { + source_policy_documents = compact([ + data.aws_iam_policy_document.default.json, + var.additional_trust_policy_json, + ]) +} diff --git a/modules/compute-providers/microvm/trust-policy/outputs.tf b/modules/compute-providers/microvm/trust-policy/outputs.tf new file mode 100644 index 0000000000..8564675873 --- /dev/null +++ b/modules/compute-providers/microvm/trust-policy/outputs.tf @@ -0,0 +1,4 @@ +output "assume_role_policy" { + description = "MicroVM runner-role trust policy including any additional trust statements." + value = data.aws_iam_policy_document.assume_role.json +} diff --git a/modules/compute-providers/microvm/trust-policy/tests/trust-policy.tftest.hcl b/modules/compute-providers/microvm/trust-policy/tests/trust-policy.tftest.hcl new file mode 100644 index 0000000000..5f0173ebc1 --- /dev/null +++ b/modules/compute-providers/microvm/trust-policy/tests/trust-policy.tftest.hcl @@ -0,0 +1,59 @@ +mock_provider "aws" { + mock_data "aws_iam_policy_document" { + defaults = { + json = "{}" + } + } +} + +run "returns_default_microvm_trust_policy" { + command = plan + + assert { + condition = toset(data.aws_iam_policy_document.default.statement[0].actions) == toset(["sts:AssumeRole", "sts:TagSession"]) + error_message = "The MicroVM runner role must allow assume-role and tagged sessions." + } + + assert { + condition = anytrue([ + for principal in data.aws_iam_policy_document.default.statement[0].principals : + principal.type == "Service" && toset(principal.identifiers) == toset(["lambda.amazonaws.com"]) + ]) + error_message = "The MicroVM runner role must trust the Lambda service principal required by the provider." + } + + assert { + condition = ( + length(data.aws_iam_policy_document.assume_role.source_policy_documents) == 1 + && output.assume_role_policy == data.aws_iam_policy_document.assume_role.json + ) + error_message = "The MicroVM trust-policy module must return the default trust document as assume_role_policy." + } +} + +run "merges_additional_trust_policy" { + command = plan + + variables { + additional_trust_policy_json = "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"TrustDeploymentRole\",\"Effect\":\"Allow\",\"Action\":\"sts:AssumeRole\",\"Principal\":{\"AWS\":\"arn:aws:iam::123456789012:role/deployer\"}}]}" + } + + assert { + condition = ( + length(data.aws_iam_policy_document.assume_role.source_policy_documents) == 2 + && contains(data.aws_iam_policy_document.assume_role.source_policy_documents, var.additional_trust_policy_json) + && output.assume_role_policy == data.aws_iam_policy_document.assume_role.json + ) + error_message = "The MicroVM trust-policy module must merge and return the additional trust policy document." + } +} + +run "rejects_invalid_additional_trust_policy" { + command = plan + + variables { + additional_trust_policy_json = "{" + } + + expect_failures = [var.additional_trust_policy_json] +} diff --git a/modules/compute-providers/microvm/trust-policy/variables.tf b/modules/compute-providers/microvm/trust-policy/variables.tf new file mode 100644 index 0000000000..1af67309d2 --- /dev/null +++ b/modules/compute-providers/microvm/trust-policy/variables.tf @@ -0,0 +1,10 @@ +variable "additional_trust_policy_json" { + description = "Optional IAM policy document merged with the MicroVM provider's default runner-role trust policy." + type = string + default = null + + validation { + condition = var.additional_trust_policy_json == null ? true : can(jsondecode(var.additional_trust_policy_json)) + error_message = "additional_trust_policy_json must be valid JSON when set." + } +} diff --git a/modules/compute-providers/microvm/trust-policy/versions.tf b/modules/compute-providers/microvm/trust-policy/versions.tf new file mode 100644 index 0000000000..18ab313d76 --- /dev/null +++ b/modules/compute-providers/microvm/trust-policy/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.4.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.0" + } + } +} diff --git a/modules/compute-providers/microvm/validations.tf b/modules/compute-providers/microvm/validations.tf new file mode 100644 index 0000000000..4acbaecfa7 --- /dev/null +++ b/modules/compute-providers/microvm/validations.tf @@ -0,0 +1,48 @@ +resource "terraform_data" "validate_config" { + lifecycle { + precondition { + condition = trimspace(var.config.image_identifier) != "" + error_message = "compute_provider.microvm.image_identifier must not be empty." + } + + precondition { + condition = var.config.maximum_duration_in_seconds == null ? true : ( + var.config.maximum_duration_in_seconds >= 1 && + var.config.maximum_duration_in_seconds <= 28800 + ) + error_message = "compute_provider.microvm.maximum_duration_in_seconds must be null or between 1 and 28800." + } + + precondition { + condition = var.config.run_hook_payload == null ? true : length(var.config.run_hook_payload) <= 16384 + error_message = "compute_provider.microvm.run_hook_payload must be 16384 characters or less." + } + + precondition { + condition = var.config.logging == null ? true : ( + (var.config.logging.cloud_watch == null ? 0 : 1) + + (var.config.logging.disabled ? 1 : 0) == 1 + ) + error_message = "compute_provider.microvm.logging must set exactly one of cloud_watch or disabled." + } + + precondition { + condition = try(var.config.iam.additional_policy_json.scale_up, null) == null ? true : can(jsondecode(var.config.iam.additional_policy_json.scale_up)) + error_message = "compute_provider.microvm.iam.additional_policy_json.scale_up must be valid JSON when set." + } + } +} + +resource "terraform_data" "validate_runner" { + lifecycle { + precondition { + condition = contains(["linux", "osx", "windows"], var.runner.os) + error_message = "runner.os must be linux, osx, or windows." + } + + precondition { + condition = length(var.runner.name_prefix) <= 45 + error_message = "runner.name_prefix must be at most 45 characters." + } + } +} diff --git a/modules/compute-providers/microvm/variables.tf b/modules/compute-providers/microvm/variables.tf new file mode 100644 index 0000000000..8e0aed08f6 --- /dev/null +++ b/modules/compute-providers/microvm/variables.tf @@ -0,0 +1,200 @@ +variable "aws_partition" { + description = "AWS partition used to construct IAM ARNs." + type = string + default = "aws" +} + +variable "aws_region" { + description = "AWS region used by compute-provider resources and policy documents." + type = string +} + +variable "prefix" { + description = "Prefix used to identify resources created for the runner stack." + type = string + default = "github-actions" +} + +variable "tags" { + description = "Base tags available to taggable compute-provider resources. Provider-specific tags override this map within their documented scopes." + type = map(string) + default = {} +} + +variable "config" { + description = <<-EOT + Lambda MicroVM compute-provider configuration. Paths match `compute_provider.microvm` in the runner stack. + + - `image_identifier`: ARN or ID of the MicroVM image used to run GitHub runners. + - `image_version`: Optional MicroVM image version. + - `execution_role`: Optional externally managed execution role assumed by MicroVMs. Null uses the common runner role. + - `execution_role.arn`: ARN of the externally managed MicroVM execution role. + - `egress_network_connectors`: Egress network connectors passed to RunMicrovm. + - `idle_policy`: Optional auto-suspend and auto-resume configuration passed to RunMicrovm. + - `idle_policy.max_idle_duration_seconds`: Maximum idle time before MicroVM auto-suspend. + - `idle_policy.suspended_duration_seconds`: Maximum suspended time before MicroVM termination. + - `idle_policy.auto_resume_enabled`: Enables automatic resume on inbound traffic while suspended. + - `logging`: Optional RunMicrovm logging union. Exactly one of `cloud_watch` or `disabled` must be selected when set. + - `logging.cloud_watch.log_group`: Optional CloudWatch Logs log group used by MicroVM runtime logs. + - `logging.cloud_watch.log_stream`: Optional CloudWatch Logs log stream used by MicroVM runtime logs. + - `logging.disabled`: Disables MicroVM runtime logging when true. + - `run_hook_payload`: Optional payload delivered to the MicroVM `/run` hook. Maximum 16,384 characters. + - `maximum_duration_in_seconds`: Optional maximum MicroVM lifetime. Valid range is 1 through 28,800 seconds. + - `environment_variables`: Additional provider-specific Lambda environment variables merged into scale-up, scale-down, and pool. + - `tags`: Tags encoded into the MicroVM runner configuration. + - `iam.resource_arns`: Resource ARNs used by the generated MicroVM control-plane policies. The service is new and some actions may require `*`. + - `iam.actions.scale_up`: MicroVM IAM actions used by scale-up and pool. + - `iam.actions.scale_down`: MicroVM IAM actions used by scale-down. + - `iam.additional_policy_json.scale_up`: Optional additional provider policy attached separately to the scale-up Lambda role. + - `iam.managed_policy_arns.scale_up`: Optional managed policy attached to the scale-up Lambda role. + - `iam.managed_policy_arns.pool`: Optional managed policy attached to the pool Lambda role. + EOT + + type = object({ + image_identifier = string + image_version = optional(string, null) + execution_role = optional(object({ + arn = string + }), null) + egress_network_connectors = optional(list(string), []) + idle_policy = optional(object({ + max_idle_duration_seconds = number + suspended_duration_seconds = number + auto_resume_enabled = bool + }), null) + logging = optional(object({ + cloud_watch = optional(object({ + log_group = optional(string, null) + log_stream = optional(string, null) + }), null) + disabled = optional(bool, false) + }), null) + run_hook_payload = optional(string, null) + maximum_duration_in_seconds = optional(number, null) + environment_variables = optional(map(string), {}) + tags = optional(map(string), {}) + iam = optional(object({ + resource_arns = optional(list(string), ["*"]) + actions = optional(object({ + scale_up = optional(list(string), null) + scale_down = optional(list(string), null) + }), {}) + additional_policy_json = optional(object({ + scale_up = optional(string, null) + }), {}) + managed_policy_arns = optional(object({ + scale_up = optional(string, null) + pool = optional(string, null) + }), {}) + }), {}) + }) + + nullable = false +} + +variable "runner" { + description = <<-EOT + Provider-neutral runner settings consumed by compute providers. + + - `os`: Runner operating system. Supported values are `linux`, `osx`, and `windows`. + - `architecture`: Runner distribution architecture. + - `boot_time_in_minutes`: Expected boot and registration duration used by scale-down and pool. + - `name_prefix`: Prefix added to registered runner names. + - `run_as_root`: Runs the runner service as root. + - `run_as`: Operating-system user used when `run_as_root` is false. + - `hooks.job_started`: Script installed as the runner job-started hook. + - `hooks.job_completed`: Script installed as the runner job-completed hook. + - `iam.role.arn`: Resolved runner-role ARN referenced by provider policies and resources. + - `iam.role.name`: Resolved runner-role name used by provider resources. + - `iam.role.managed`: Whether runner-stack manages the resolved runner role. + - `iam.managed_policy_arns`: Common managed-policy ARNs returned with the provider-specific runner policies for attachment by runner-stack. + - `iam.path`: IAM path available to provider-managed IAM resources. Null derives the path from `prefix`. + EOT + type = object({ + os = optional(string, "linux") + architecture = optional(string, "x64") + boot_time_in_minutes = optional(number, 5) + name_prefix = optional(string, "") + run_as_root = optional(bool, false) + run_as = optional(string, "ec2-user") + hooks = optional(object({ + job_started = optional(string, "") + job_completed = optional(string, "") + }), {}) + iam = object({ + role = object({ + arn = string + name = string + managed = optional(bool, true) + }) + managed_policy_arns = optional(map(string), {}) + path = optional(string, null) + }) + }) + + nullable = false +} + +# tflint-ignore: terraform_unused_declarations +variable "github" { + description = <<-EOT + GitHub Enterprise Server settings available to compute-provider bootstrap data. + + - `enterprise_server.url`: Optional GitHub Enterprise Server base URL. Null selects GitHub.com. + - `enterprise_server.ssl_verify`: Enables TLS certificate verification for GitHub Enterprise Server. + EOT + type = object({ + enterprise_server = optional(object({ + url = optional(string, null) + ssl_verify = optional(bool, true) + }), {}) + }) + default = {} + nullable = false +} + +# tflint-ignore: terraform_unused_declarations +variable "ssm" { + description = <<-EOT + Parameter Store paths and tag scopes available to compute-provider bootstrap resources. + + - `paths.root`: Root Parameter Store path for the runner stack. + - `paths.tokens`: Path segment used for registration tokens and just-in-time configuration. + - `paths.config`: Path segment used for persistent runner and provider configuration. + - `tags`: Shared SSM tags that override module-level `tags`. + - `parameters.tags`: Parameter-specific tags that override module-level and shared SSM tags. + EOT + type = object({ + paths = object({ + root = string + tokens = string + config = string + }) + tags = optional(map(string), {}) + parameters = optional(object({ + tags = optional(map(string), {}) + }), {}) + }) + + nullable = false +} + +# tflint-ignore: terraform_unused_declarations +variable "observability" { + description = <<-EOT + CloudWatch Logs settings available to compute-provider runner log groups. + + - `logs.retention_in_days`: Retention period for provider-owned runner log groups. + - `logs.kms_key_id`: Optional KMS key ID or ARN used to encrypt runner log groups. + - `logs.tags`: Shared log-group tags that override module-level `tags`. + EOT + type = object({ + logs = optional(object({ + retention_in_days = optional(number, 180) + kms_key_id = optional(string, null) + tags = optional(map(string), {}) + }), {}) + }) + default = {} + nullable = false +} diff --git a/modules/compute-providers/microvm/versions.tf b/modules/compute-providers/microvm/versions.tf new file mode 100644 index 0000000000..3ef011ea0a --- /dev/null +++ b/modules/compute-providers/microvm/versions.tf @@ -0,0 +1,10 @@ +terraform { + required_version = ">= 1.4.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 6.33" + } + } +} diff --git a/modules/multi-runner/tests/provider-routing.tftest.hcl b/modules/multi-runner/tests/provider-routing.tftest.hcl index 7a23617a8f..c00e41f4b9 100644 --- a/modules/multi-runner/tests/provider-routing.tftest.hcl +++ b/modules/multi-runner/tests/provider-routing.tftest.hcl @@ -326,6 +326,83 @@ run "experimental_v2_routes_through_provider_stack" { } } +run "experimental_v2_routes_microvm_through_provider_stack" { + command = plan + + variables { + experimental = { + multi_runner_config_v2 = { + micro = { + runner = { + os = "linux" + architecture = "arm64" + maximum_count = 4 + name_prefix = "microvm-" + } + pool = { + config = [{ + schedule_expression = "cron(0 8 * * ? *)" + size = 2 + }] + } + compute_provider = { + microvm = { + image_identifier = "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner" + image_version = "1" + environment_variables = { + MICROVM_CLUSTER = "runner-cluster" + } + } + } + matcherConfig = { + labelMatchers = [["self-hosted", "linux", "arm64", "microvm"]] + } + } + } + } + } + + assert { + condition = ( + keys(try(local.runner_config_by_provider.ec2, {})) == [] + && keys(local.runner_config_by_provider.microvm) == ["micro"] + && local.compute_provider_types["micro"] == "microvm" + && local.runner_matcher_config["micro"].computeProvider == "microvm" + ) + error_message = "Experimental multi_runner_config_v2 entries must route MicroVM lanes to the MicroVM provider." + } + + assert { + condition = ( + length(module.runners) == 0 + && keys(module.runner_stacks) == ["micro"] + && length(module.runner_binaries) == 0 + ) + error_message = "MicroVM v2 lanes must dispatch through runner_stack without creating EC2 runner binaries." + } + + assert { + condition = ( + keys(output.runners_map_v2) == ["micro"] + && toset(keys(output.runners_map_v2["micro"].provider)) == toset(["microvm"]) + && output.runners_map_v2["micro"].provider.microvm.image_identifier == "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner" + && output.runners_map_v2["micro"].provider.microvm.image_version == "1" + ) + error_message = "MicroVM v2 lanes must expose MicroVM-owned resources under runners_map_v2..provider.microvm." + } + + assert { + condition = ( + module.runner_stacks["micro"].scale_up.lambda.environment[0].variables["COMPUTE_PROVIDER_TYPE"] == "microvm" + && module.runner_stacks["micro"].scale_up.lambda.environment[0].variables["MICROVM_IMAGE_IDENTIFIER"] == "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner" + && module.runner_stacks["micro"].scale_up.lambda.environment[0].variables["MICROVM_CLUSTER"] == "runner-cluster" + && module.runner_stacks["micro"].pool.lambda.environment[0].variables["MICROVM_IMAGE_IDENTIFIER"] == "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner" + && !contains(keys(module.runner_stacks["micro"].scale_up.lambda.environment[0].variables), "INSTANCE_TYPES") + ) + error_message = "MicroVM v2 lanes must pass MicroVM provider fragments to scale-up and pool without EC2 environment variables." + } +} + run "experimental_v2_layers_shared_and_component_tags" { command = plan diff --git a/modules/multi-runner/variables.experimental.tf b/modules/multi-runner/variables.experimental.tf index da5860f01f..4a07c5021f 100644 --- a/modules/multi-runner/variables.experimental.tf +++ b/modules/multi-runner/variables.experimental.tf @@ -138,6 +138,18 @@ variable "experimental" { - `compute_provider.ec2.metadata_options.http_endpoint`: Enables or disables the Instance Metadata Service endpoint. - `compute_provider.ec2.metadata_options.http_tokens`: Controls whether IMDSv2 session tokens are optional or required. - `compute_provider.ec2.metadata_options.http_put_response_hop_limit`: Network hop limit for Instance Metadata Service token responses. + - `compute_provider.microvm`: Lambda MicroVM-specific configuration. + - `compute_provider.microvm.image_identifier`: ARN or ID of the MicroVM image used to run GitHub runners. + - `compute_provider.microvm.image_version`: Optional MicroVM image version. + - `compute_provider.microvm.execution_role.arn`: Optional externally managed execution role assumed by MicroVMs. Null uses the common runner role. + - `compute_provider.microvm.egress_network_connectors`: Egress network connectors passed to RunMicrovm. + - `compute_provider.microvm.idle_policy`: Optional auto-suspend and auto-resume configuration passed to RunMicrovm. + - `compute_provider.microvm.logging`: Optional RunMicrovm logging union. Exactly one of `cloud_watch` or `disabled` must be selected when set. + - `compute_provider.microvm.run_hook_payload`: Optional payload delivered to the MicroVM `/run` hook. Maximum 16,384 characters. + - `compute_provider.microvm.maximum_duration_in_seconds`: Optional maximum MicroVM lifetime. Valid range is 1 through 28,800 seconds. + - `compute_provider.microvm.environment_variables`: Additional provider-specific Lambda environment variables merged into scale-up, scale-down, and pool. + - `compute_provider.microvm.tags`: Tags encoded into the MicroVM runner configuration. + - `compute_provider.microvm.iam`: Optional MicroVM control-plane IAM overrides and managed policy attachments. - `matcherConfig.labelMatchers`: Groups of labels used to match webhook jobs to this configuration. - `matcherConfig.exactMatch`: Requires the job labels to exactly match a configured label group. - `matcherConfig.bidirectionalLabelMatch`: Requires labels to match in both directions instead of allowing configured subsets. @@ -370,6 +382,45 @@ variable "experimental" { })), null) tags = optional(map(string), {}) }), null) + + microvm = optional(object({ + image_identifier = string + image_version = optional(string, null) + execution_role = optional(object({ + arn = string + }), null) + egress_network_connectors = optional(list(string), []) + idle_policy = optional(object({ + max_idle_duration_seconds = number + suspended_duration_seconds = number + auto_resume_enabled = bool + }), null) + logging = optional(object({ + cloud_watch = optional(object({ + log_group = optional(string, null) + log_stream = optional(string, null) + }), null) + disabled = optional(bool, false) + }), null) + run_hook_payload = optional(string, null) + maximum_duration_in_seconds = optional(number, null) + environment_variables = optional(map(string), {}) + tags = optional(map(string), {}) + iam = optional(object({ + resource_arns = optional(list(string), ["*"]) + actions = optional(object({ + scale_up = optional(list(string), null) + scale_down = optional(list(string), null) + }), {}) + additional_policy_json = optional(object({ + scale_up = optional(string, null) + }), {}) + managed_policy_arns = optional(object({ + scale_up = optional(string, null) + pool = optional(string, null) + }), {}) + }), {}) + }), null) }) matcherConfig = object({ @@ -399,7 +450,7 @@ variable "experimental" { if provider_config != null ]) == 1 ]) - error_message = "Each experimental runner configuration must set exactly one compute-provider block. Supported compute-provider blocks: ec2." + error_message = "Each experimental runner configuration must set exactly one compute-provider block. Supported compute-provider blocks: ec2, microvm." } validation { diff --git a/modules/runner-stack/compute-provider.tf b/modules/runner-stack/compute-provider.tf index 78e00bf03c..af53e415e3 100644 --- a/modules/runner-stack/compute-provider.tf +++ b/modules/runner-stack/compute-provider.tf @@ -5,13 +5,15 @@ locals { ]) provider_assume_role_policies = { - ec2 = try(module.ec2_trust_policy[0].assume_role_policy, null) + ec2 = try(module.ec2_trust_policy[0].assume_role_policy, null) + microvm = try(module.microvm_trust_policy[0].assume_role_policy, null) } provider_assume_role_policy = local.provider_assume_role_policies[local.provider_type] provider_contracts = { - ec2 = one(module.ec2[*].provider) + ec2 = one(module.ec2[*].provider) + microvm = one(module.microvm[*].provider) } provider_contract = local.provider_contracts[local.provider_type] diff --git a/modules/runner-stack/microvm.tf b/modules/runner-stack/microvm.tf new file mode 100644 index 0000000000..f768d5092c --- /dev/null +++ b/modules/runner-stack/microvm.tf @@ -0,0 +1,27 @@ +module "microvm_trust_policy" { + count = local.provider_type == "microvm" ? 1 : 0 + source = "../compute-providers/microvm/trust-policy" + + additional_trust_policy_json = var.runner.iam.additional_trust_policy_json +} + +module "microvm" { + count = local.provider_type == "microvm" ? 1 : 0 + source = "../compute-providers/microvm" + + aws_partition = var.aws_partition + aws_region = var.aws_region + prefix = var.prefix + tags = var.tags + + config = var.compute_provider.microvm + runner = merge(var.runner, { + iam = merge(var.runner.iam, { + role = local.runner_role + managed_policy_arns = local.common_runner_managed_policy_arns + }) + }) + github = var.github + ssm = var.ssm + observability = var.observability +} diff --git a/modules/runner-stack/tests/pool.tftest.hcl b/modules/runner-stack/tests/pool.tftest.hcl index 6e14f18fcb..f93ca2fff6 100644 --- a/modules/runner-stack/tests/pool.tftest.hcl +++ b/modules/runner-stack/tests/pool.tftest.hcl @@ -129,6 +129,7 @@ run "plan_with_pool_enabled" { assert { condition = ( length(module.ec2_trust_policy) == 1 + && length(module.microvm_trust_policy) == 0 && aws_iam_role.runner[0].assume_role_policy == module.ec2_trust_policy[0].assume_role_policy ) error_message = "The common runner role must use the selected EC2 trust-policy submodule output." @@ -198,6 +199,84 @@ run "plan_with_pool_enabled" { } +run "plan_with_microvm_provider_enabled" { + command = plan + + variables { + runner = { + labels = ["self-hosted", "linux", "arm64", "microvm"] + name_prefix = "microvm-" + iam = { + managed_policy_arns = { + readonly = "arn:aws:iam::aws:policy/ReadOnlyAccess" + } + } + } + + compute_provider = { + microvm = { + image_identifier = "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner" + image_version = "1" + environment_variables = { + MICROVM_CLUSTER = "runner-cluster" + } + tags = { + Lane = "microvm" + } + } + } + } + + assert { + condition = ( + length(module.ec2) == 0 + && length(module.microvm) == 1 + && toset(keys(output.provider)) == toset(["microvm"]) + && toset(keys(output.provider.microvm)) == toset(["image_identifier", "image_version", "execution_role_arn"]) + ) + error_message = "The runner stack must instantiate only the selected MicroVM provider and expose resources under provider.microvm." + } + + assert { + condition = ( + length(module.ec2_trust_policy) == 0 + && length(module.microvm_trust_policy) == 1 + && aws_iam_role.runner[0].assume_role_policy == module.microvm_trust_policy[0].assume_role_policy + ) + error_message = "The common runner role must use the selected MicroVM trust-policy submodule output." + } + + assert { + condition = ( + length(aws_iam_role_policy.runner_provider) == 0 + && aws_iam_role_policy_attachment.runner["user-readonly"].policy_arn == "arn:aws:iam::aws:policy/ReadOnlyAccess" + && output.provider.microvm.image_identifier == "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner" + && output.provider.microvm.image_version == "1" + ) + error_message = "MicroVM must return common runner policies without EC2 policies and expose its selected image metadata." + } + + assert { + condition = ( + module.scale_runners.scale_up.lambda.environment[0].variables["COMPUTE_PROVIDER_TYPE"] == "microvm" + && module.scale_runners.scale_up.lambda.environment[0].variables["MICROVM_IMAGE_IDENTIFIER"] == "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner" + && module.scale_runners.scale_up.lambda.environment[0].variables["MICROVM_CLUSTER"] == "runner-cluster" + && module.scale_runners.scale_down.lambda.environment[0].variables["RUNNER_BOOT_TIME_IN_MINUTES"] == "5" + && !contains(keys(module.scale_runners.scale_up.lambda.environment[0].variables), "INSTANCE_TYPES") + ) + error_message = "Scale-up and scale-down must receive MicroVM provider fragments without EC2 environment variables." + } + + assert { + condition = ( + output.pool != null + && module.pool[0].pool.lambda.environment[0].variables["MICROVM_IMAGE_IDENTIFIER"] == "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner" + && module.pool[0].pool.lambda.environment[0].variables["RUNNER_BOOT_TIME_IN_MINUTES"] == "5" + ) + error_message = "The pool component must receive MicroVM provider fragments when a MicroVM lane has pool config." + } +} + run "external_runner_role_is_not_managed_by_common" { command = plan diff --git a/modules/runner-stack/variables.compute-provider.tf b/modules/runner-stack/variables.compute-provider.tf index f8cb67d9a2..afcec2dc3d 100644 --- a/modules/runner-stack/variables.compute-provider.tf +++ b/modules/runner-stack/variables.compute-provider.tf @@ -104,6 +104,18 @@ variable "compute_provider" { - `ec2.enable_on_demand_failover_for_errors`: EC2 error codes that trigger an on-demand fallback after a Spot launch failure. - `ec2.scale_errors`: EC2 error codes treated as retryable scale-up failures. - `ec2.use_dedicated_host`: Enables the dedicated-host launch path, required for macOS runners. + - `microvm`: Lambda MicroVM compute-provider configuration. + - `microvm.image_identifier`: ARN or ID of the MicroVM image used to run GitHub runners. + - `microvm.image_version`: Optional MicroVM image version. + - `microvm.execution_role.arn`: Optional externally managed execution role assumed by MicroVMs. Null uses the common runner role. + - `microvm.egress_network_connectors`: Egress network connectors passed to RunMicrovm. + - `microvm.idle_policy`: Optional auto-suspend and auto-resume configuration passed to RunMicrovm. + - `microvm.logging`: Optional RunMicrovm logging union. Exactly one of `cloud_watch` or `disabled` must be selected when set. + - `microvm.run_hook_payload`: Optional payload delivered to the MicroVM `/run` hook. Maximum 16,384 characters. + - `microvm.maximum_duration_in_seconds`: Optional maximum MicroVM lifetime. Valid range is 1 through 28,800 seconds. + - `microvm.environment_variables`: Additional provider-specific Lambda environment variables merged into scale-up, scale-down, and pool. + - `microvm.tags`: Tags encoded into the MicroVM runner configuration. + - `microvm.iam`: Optional MicroVM control-plane IAM overrides and managed policy attachments. EOT type = object({ @@ -243,6 +255,45 @@ variable "compute_provider" { ]) use_dedicated_host = optional(bool, false) }), null) + + microvm = optional(object({ + image_identifier = string + image_version = optional(string, null) + execution_role = optional(object({ + arn = string + }), null) + egress_network_connectors = optional(list(string), []) + idle_policy = optional(object({ + max_idle_duration_seconds = number + suspended_duration_seconds = number + auto_resume_enabled = bool + }), null) + logging = optional(object({ + cloud_watch = optional(object({ + log_group = optional(string, null) + log_stream = optional(string, null) + }), null) + disabled = optional(bool, false) + }), null) + run_hook_payload = optional(string, null) + maximum_duration_in_seconds = optional(number, null) + environment_variables = optional(map(string), {}) + tags = optional(map(string), {}) + iam = optional(object({ + resource_arns = optional(list(string), ["*"]) + actions = optional(object({ + scale_up = optional(list(string), null) + scale_down = optional(list(string), null) + }), {}) + additional_policy_json = optional(object({ + scale_up = optional(string, null) + }), {}) + managed_policy_arns = optional(object({ + scale_up = optional(string, null) + pool = optional(string, null) + }), {}) + }), {}) + }), null) }) validation { @@ -250,6 +301,6 @@ variable "compute_provider" { for provider_type, provider_config in var.compute_provider : provider_type if provider_config != null ]) == 1 - error_message = "Exactly one compute-provider block must be set. Supported compute-provider blocks: ec2." + error_message = "Exactly one compute-provider block must be set. Supported compute-provider blocks: ec2, microvm." } } diff --git a/modules/webhook/variables.tf b/modules/webhook/variables.tf index a2fae87e4a..2983af59a2 100644 --- a/modules/webhook/variables.tf +++ b/modules/webhook/variables.tf @@ -23,7 +23,7 @@ variable "tags" { } variable "runner_matcher_config" { - description = "SQS queue to publish accepted build events based on the runner type. `computeProvider` defaults to `ec2`; EC2 is the only provider currently implemented. When exact match is disabled the webhook accepts the event if one of the workflow job labels is part of the matcher. The priority defines the order the matchers are applied. Optional `matcherConfig.enableDynamicLabels` and `matcherConfig.awsDynamicLabelsPolicy` are evaluated by the dispatcher to gate provider dynamic labels per runner. The policy supports `blocked_keys = []` and `restricted_keys = { = { allowed = [globs], denied = [globs], max = number|string } }`; keys use the provider dynamic label suffix form, for example `instance-type` for `ghr-ec2-instance-type`." + description = "SQS queue to publish accepted build events based on the runner type. `computeProvider` defaults to `ec2`; supported values are `ec2` and `microvm`. When exact match is disabled the webhook accepts the event if one of the workflow job labels is part of the matcher. The priority defines the order the matchers are applied. Optional `matcherConfig.enableDynamicLabels` and `matcherConfig.awsDynamicLabelsPolicy` are evaluated by the dispatcher to gate provider dynamic labels per runner. The policy supports `blocked_keys = []` and `restricted_keys = { = { allowed = [globs], denied = [globs], max = number|string } }`; keys use the provider dynamic label suffix form, for example `instance-type` for `ghr-ec2-instance-type`." type = map(object({ arn = string id = string From d7d5b9cc534bc60792f465eea60cad68b39b006c Mon Sep 17 00:00:00 2001 From: edersonbrilhante Date: Thu, 13 Aug 2026 14:19:17 +0200 Subject: [PATCH 2/2] docs(microvm): document Terraform compute provider --- .../internal/compute-provider-refactor.md | 8 +-- modules/compute-providers/microvm/README.md | 51 +++++++++++++++++++ .../microvm/trust-policy/README.md | 41 +++++++++++++++ modules/multi-runner/README.md | 6 +-- modules/runner-stack/README.md | 6 ++- modules/webhook/README.md | 2 +- 6 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 modules/compute-providers/microvm/README.md create mode 100644 modules/compute-providers/microvm/trust-policy/README.md diff --git a/docs/modules/internal/compute-provider-refactor.md b/docs/modules/internal/compute-provider-refactor.md index 7f20e56e0c..791d247d14 100644 --- a/docs/modules/internal/compute-provider-refactor.md +++ b/docs/modules/internal/compute-provider-refactor.md @@ -8,7 +8,7 @@ The scale-up, scale-down, pool, job-retry, queue, SSM housekeeping, and GitHub registration workflows are not inherently EC2-specific. The legacy `runners` module combines that common control plane with EC2 launch templates, instance profiles, bootstrap parameters, log groups, IAM permissions, and Lambda environment variables. Adding another compute provider in that structure would require copying common behavior or adding provider conditionals throughout the module. -The refactor introduces a provider boundary so a future MicroVM or other backend can reuse the control plane. Only the policy statements, environment variables, and resources required by the selected compute provider should change. +The refactor introduces a provider boundary so MicroVM and other backends can reuse the control plane. Only the policy statements, environment variables, and resources required by the selected compute provider should change. ## Ownership model @@ -25,11 +25,11 @@ The implementation is split into orchestration, provider-neutral control-plane c | `compute-providers//trust-policy` | Provider-specific default runner-role trust, merged with the optional caller-provided trust document before the common role is created. | | `compute-providers/` | Provider-specific resources, permission requirements, and the IAM and environment-variable fragments consumed by the common control plane after the runner role is resolved. | -The EC2 provider owns the instance profile, launch template, security group, AMI and bootstrap parameters, runner log groups, EC2 policy statements, and EC2 Lambda environment variables. EC2 is the only implemented Terraform compute provider in this phase. +The EC2 provider owns the instance profile, launch template, security group, AMI and bootstrap parameters, runner log groups, EC2 policy statements, and EC2 Lambda environment variables. The MicroVM provider owns the Lambda MicroVM runtime configuration, execution-role policy, and MicroVM Lambda environment variables. Terraform does not manage MicroVM lifecycle resources directly; the runtime control plane creates and terminates MicroVM runners. The modules below `runner-stack` are internal implementation boundaries, not standalone public modules. Callers opt into the experimental interface through `experimental.multi_runner_config_v2`; `multi-runner` calls `runner-stack`, which composes the internal modules. Their direct input and output contracts may change while v2 remains experimental. -`runner-stack` selects a compute provider from the single populated typed block under `compute_provider`. For example, `compute_provider = { ec2 = { ... } }` selects EC2; there is no separate `type` input that can disagree with the populated block. Exactly one provider block must be populated, and its presence must be known during planning because it determines the module graph. Native input validation enforces this common selection rule, while each compute-provider module owns its provider-specific semantic validation. The stack passes `compute_provider.` to the selected provider module as one nested `config` object. It also passes the provider-neutral `runner`, `github`, `ssm`, and `observability` objects without expanding them back into prefixed scalar inputs. This keeps ownership visible at the module boundary and gives future compute providers an equivalent contract to implement. +`runner-stack` selects a compute provider from the single populated typed block under `compute_provider`. For example, `compute_provider = { ec2 = { ... } }` selects EC2 and `compute_provider = { microvm = { ... } }` selects MicroVM; there is no separate `type` input that can disagree with the populated block. Exactly one provider block must be populated, and its presence must be known during planning because it determines the module graph. Native input validation enforces this common selection rule, while each compute-provider module owns its provider-specific semantic validation. The stack passes `compute_provider.` to the selected provider module as one nested `config` object. It also passes the provider-neutral `runner`, `github`, `ssm`, and `observability` objects without expanding them back into prefixed scalar inputs. This keeps ownership visible at the module boundary and gives future compute providers an equivalent contract to implement. The common stack creates or selects the runner IAM role, but the selected provider owns the role's default trust-policy document. Each provider implements a small `trust-policy` submodule that accepts `additional_trust_policy_json` and returns the final `assume_role_policy`. The full provider separately returns its nested `provider` contract containing `policies.runner`, `policies.scale_up`, `policies.scale_down`, and `policies.pool`, component environment variables, and provider resources. The common stack uses the isolated trust-policy output when it creates the runner role and attaches the full provider's permission documents to the roles owned by the corresponding common components. A provider never creates or attaches a common IAM role. @@ -123,7 +123,7 @@ Tags follow the same ownership model. Module tags are defaults; shared Lambda, q Application logging settings stay together under `observability.logs`, including `level`, retention, encryption, class, and shared log-group tags. -In v1 mode, entries remain exclusively in `runners_map` and retain their flat output fields; `runners_map_v2` is empty. In v2 mode, entries are exposed exclusively through `runners_map_v2` and `runners_map` is empty. Common resources are grouped under `runner`, `scale_up`, `scale_down`, and `pool`, while provider-specific resources remain under `provider.`. The provider key is derived dynamically from the selected input block and therefore also identifies the compute provider. For example, the common runner role is available at `runners_map_v2["configuration"].runner.role`, while an EC2 selection places launch-template and runner-log artifacts under `runners_map_v2["configuration"].provider.ec2`. The `pool` value is null when no pool configuration is supplied. +In v1 mode, entries remain exclusively in `runners_map` and retain their flat output fields; `runners_map_v2` is empty. In v2 mode, entries are exposed exclusively through `runners_map_v2` and `runners_map` is empty. Common resources are grouped under `runner`, `scale_up`, `scale_down`, and `pool`, while provider-specific resources remain under `provider.`. The provider key is derived dynamically from the selected input block and therefore also identifies the compute provider. For example, the common runner role is available at `runners_map_v2["configuration"].runner.role`, an EC2 selection places launch-template and runner-log artifacts under `runners_map_v2["configuration"].provider.ec2`, and a MicroVM selection places image and execution-role references under `runners_map_v2["configuration"].provider.microvm`. The `pool` value is null when no pool configuration is supplied. ## Plan-time provider selection and ownership wrappers diff --git a/modules/compute-providers/microvm/README.md b/modules/compute-providers/microvm/README.md new file mode 100644 index 0000000000..042bd973c5 --- /dev/null +++ b/modules/compute-providers/microvm/README.md @@ -0,0 +1,51 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.4.0 | +| [aws](#requirement\_aws) | >= 6.33 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.33 | +| [terraform](#provider\_terraform) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [terraform_data.validate_config](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | +| [terraform_data.validate_runner](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource | +| [aws_iam_policy_document.scale_down](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.scale_up](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aws\_partition](#input\_aws\_partition) | AWS partition used to construct IAM ARNs. | `string` | `"aws"` | no | +| [aws\_region](#input\_aws\_region) | AWS region used by compute-provider resources and policy documents. | `string` | n/a | yes | +| [config](#input\_config) | Lambda MicroVM compute-provider configuration. Paths match `compute_provider.microvm` in the runner stack.

- `image_identifier`: ARN or ID of the MicroVM image used to run GitHub runners.
- `image_version`: Optional MicroVM image version.
- `execution_role`: Optional externally managed execution role assumed by MicroVMs. Null uses the common runner role.
- `execution_role.arn`: ARN of the externally managed MicroVM execution role.
- `egress_network_connectors`: Egress network connectors passed to RunMicrovm.
- `idle_policy`: Optional auto-suspend and auto-resume configuration passed to RunMicrovm.
- `idle_policy.max_idle_duration_seconds`: Maximum idle time before MicroVM auto-suspend.
- `idle_policy.suspended_duration_seconds`: Maximum suspended time before MicroVM termination.
- `idle_policy.auto_resume_enabled`: Enables automatic resume on inbound traffic while suspended.
- `logging`: Optional RunMicrovm logging union. Exactly one of `cloud_watch` or `disabled` must be selected when set.
- `logging.cloud_watch.log_group`: Optional CloudWatch Logs log group used by MicroVM runtime logs.
- `logging.cloud_watch.log_stream`: Optional CloudWatch Logs log stream used by MicroVM runtime logs.
- `logging.disabled`: Disables MicroVM runtime logging when true.
- `run_hook_payload`: Optional payload delivered to the MicroVM `/run` hook. Maximum 16,384 characters.
- `maximum_duration_in_seconds`: Optional maximum MicroVM lifetime. Valid range is 1 through 28,800 seconds.
- `environment_variables`: Additional provider-specific Lambda environment variables merged into scale-up, scale-down, and pool.
- `tags`: Tags encoded into the MicroVM runner configuration.
- `iam.resource_arns`: Resource ARNs used by the generated MicroVM control-plane policies. The service is new and some actions may require `*`.
- `iam.actions.scale_up`: MicroVM IAM actions used by scale-up and pool.
- `iam.actions.scale_down`: MicroVM IAM actions used by scale-down.
- `iam.additional_policy_json.scale_up`: Optional additional provider policy attached separately to the scale-up Lambda role.
- `iam.managed_policy_arns.scale_up`: Optional managed policy attached to the scale-up Lambda role.
- `iam.managed_policy_arns.pool`: Optional managed policy attached to the pool Lambda role. |
object({
image_identifier = string
image_version = optional(string, null)
execution_role = optional(object({
arn = string
}), null)
egress_network_connectors = optional(list(string), [])
idle_policy = optional(object({
max_idle_duration_seconds = number
suspended_duration_seconds = number
auto_resume_enabled = bool
}), null)
logging = optional(object({
cloud_watch = optional(object({
log_group = optional(string, null)
log_stream = optional(string, null)
}), null)
disabled = optional(bool, false)
}), null)
run_hook_payload = optional(string, null)
maximum_duration_in_seconds = optional(number, null)
environment_variables = optional(map(string), {})
tags = optional(map(string), {})
iam = optional(object({
resource_arns = optional(list(string), ["*"])
actions = optional(object({
scale_up = optional(list(string), null)
scale_down = optional(list(string), null)
}), {})
additional_policy_json = optional(object({
scale_up = optional(string, null)
}), {})
managed_policy_arns = optional(object({
scale_up = optional(string, null)
pool = optional(string, null)
}), {})
}), {})
})
| n/a | yes | +| [github](#input\_github) | GitHub Enterprise Server settings available to compute-provider bootstrap data.

- `enterprise_server.url`: Optional GitHub Enterprise Server base URL. Null selects GitHub.com.
- `enterprise_server.ssl_verify`: Enables TLS certificate verification for GitHub Enterprise Server. |
object({
enterprise_server = optional(object({
url = optional(string, null)
ssl_verify = optional(bool, true)
}), {})
})
| `{}` | no | +| [observability](#input\_observability) | CloudWatch Logs settings available to compute-provider runner log groups.

- `logs.retention_in_days`: Retention period for provider-owned runner log groups.
- `logs.kms_key_id`: Optional KMS key ID or ARN used to encrypt runner log groups.
- `logs.tags`: Shared log-group tags that override module-level `tags`. |
object({
logs = optional(object({
retention_in_days = optional(number, 180)
kms_key_id = optional(string, null)
tags = optional(map(string), {})
}), {})
})
| `{}` | no | +| [prefix](#input\_prefix) | Prefix used to identify resources created for the runner stack. | `string` | `"github-actions"` | no | +| [runner](#input\_runner) | Provider-neutral runner settings consumed by compute providers.

- `os`: Runner operating system. Supported values are `linux`, `osx`, and `windows`.
- `architecture`: Runner distribution architecture.
- `boot_time_in_minutes`: Expected boot and registration duration used by scale-down and pool.
- `name_prefix`: Prefix added to registered runner names.
- `run_as_root`: Runs the runner service as root.
- `run_as`: Operating-system user used when `run_as_root` is false.
- `hooks.job_started`: Script installed as the runner job-started hook.
- `hooks.job_completed`: Script installed as the runner job-completed hook.
- `iam.role.arn`: Resolved runner-role ARN referenced by provider policies and resources.
- `iam.role.name`: Resolved runner-role name used by provider resources.
- `iam.role.managed`: Whether runner-stack manages the resolved runner role.
- `iam.managed_policy_arns`: Common managed-policy ARNs returned with the provider-specific runner policies for attachment by runner-stack.
- `iam.path`: IAM path available to provider-managed IAM resources. Null derives the path from `prefix`. |
object({
os = optional(string, "linux")
architecture = optional(string, "x64")
boot_time_in_minutes = optional(number, 5)
name_prefix = optional(string, "")
run_as_root = optional(bool, false)
run_as = optional(string, "ec2-user")
hooks = optional(object({
job_started = optional(string, "")
job_completed = optional(string, "")
}), {})
iam = object({
role = object({
arn = string
name = string
managed = optional(bool, true)
})
managed_policy_arns = optional(map(string), {})
path = optional(string, null)
})
})
| n/a | yes | +| [ssm](#input\_ssm) | Parameter Store paths and tag scopes available to compute-provider bootstrap resources.

- `paths.root`: Root Parameter Store path for the runner stack.
- `paths.tokens`: Path segment used for registration tokens and just-in-time configuration.
- `paths.config`: Path segment used for persistent runner and provider configuration.
- `tags`: Shared SSM tags that override module-level `tags`.
- `parameters.tags`: Parameter-specific tags that override module-level and shared SSM tags. |
object({
paths = object({
root = string
tokens = string
config = string
})
tags = optional(map(string), {})
parameters = optional(object({
tags = optional(map(string), {})
}), {})
})
| n/a | yes | +| [tags](#input\_tags) | Base tags available to taggable compute-provider resources. Provider-specific tags override this map within their documented scopes. | `map(string)` | `{}` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [environment\_variables](#output\_environment\_variables) | Provider-specific Lambda environment variable fragments consumed by runner-stack. | +| [policies](#output\_policies) | Provider-specific IAM policy fragments consumed by runner-stack. | +| [provider](#output\_provider) | Nested Lambda MicroVM compute-provider contract consumed by runner-stack. | +| [resources](#output\_resources) | Provider-specific MicroVM resources exposed by runner-stack. | + diff --git a/modules/compute-providers/microvm/trust-policy/README.md b/modules/compute-providers/microvm/trust-policy/README.md new file mode 100644 index 0000000000..533f8b657d --- /dev/null +++ b/modules/compute-providers/microvm/trust-policy/README.md @@ -0,0 +1,41 @@ +# MicroVM runner trust policy + +This internal submodule builds the MicroVM runner-role trust policy independently from runtime resources that consume the runner role. It preserves the default Lambda service trust and optionally merges an additional IAM trust policy document supplied by the common runner stack. + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.4.0 | +| [aws](#requirement\_aws) | >= 6.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | >= 6.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy_document.assume_role](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | +| [aws_iam_policy_document.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [additional\_trust\_policy\_json](#input\_additional\_trust\_policy\_json) | Optional IAM policy document merged with the MicroVM provider's default runner-role trust policy. | `string` | `null` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [assume\_role\_policy](#output\_assume\_role\_policy) | MicroVM runner-role trust policy including any additional trust statements. | + diff --git a/modules/multi-runner/README.md b/modules/multi-runner/README.md index 09ea035e9b..5f3b56377c 100644 --- a/modules/multi-runner/README.md +++ b/modules/multi-runner/README.md @@ -12,7 +12,7 @@ See [Experimental compute-provider refactor](https://github-aws-runners.github.i The multi-runner module owns provider-neutral runner-configuration normalization, queues, shared runner-binary discovery, and webhook routing. Stable `multi_runner_config` entries continue to use the existing `modules/runners` module at their historical `module.runners["configuration"]` addresses. -To opt into v2, leave `multi_runner_config` empty and populate `experimental.multi_runner_config_v2`. The whole module instance then uses `modules/runner-stack` at `module.runner_stacks["configuration"]`. That stack coordinates internal provider-neutral modules for scale-up and scale-down, pool, job retry, and SSM housekeeping, and owns the common runner role and attachments. It selects the provider from the single populated typed block under `compute_provider`; the EC2 provider owns EC2 policy requirements, the instance profile, launch template, bootstrap resources, and provider-specific Lambda fragments. The selected block must be known during planning because it determines the module graph. The runner-stack child modules are implementation details rather than standalone public entry points. EC2 remains the only Terraform-managed runner provider; MicroVM, CodeBuild, and other provider modules are future work. +To opt into v2, leave `multi_runner_config` empty and populate `experimental.multi_runner_config_v2`. The whole module instance then uses `modules/runner-stack` at `module.runner_stacks["configuration"]`. That stack coordinates internal provider-neutral modules for scale-up and scale-down, pool, job retry, and SSM housekeeping, and owns the common runner role and attachments. It selects the provider from the single populated typed block under `compute_provider`; the EC2 provider owns EC2 policy requirements, the instance profile, launch template, bootstrap resources, and provider-specific Lambda fragments. The MicroVM provider owns the Lambda MicroVM runtime configuration, execution-role policy, and provider-specific Lambda fragments while the runtime Lambdas create and terminate MicroVMs. The selected block must be known during planning because it determines the module graph. The runner-stack child modules are implementation details rather than standalone public entry points. CodeBuild and other provider modules are future work. In v2, common runner-role configuration belongs under `runner.iam`; EC2's optional external instance-profile selection belongs under `compute_provider.ec2.instance_profile`. Provider policy documents are generated internally and attached by the common stack when it creates the role. An external role remains unmanaged and must already contain the required policies. @@ -22,7 +22,7 @@ Phase 1 supports both input contracts, but callers must populate only one runner For v2 runner configurations, top-level module `tags` are merged with configuration `tags`. Shared `lambda.tags`, `queue.tags`, and `observability.logs.tags` are then merged with component tags such as `runner.tags`, `scale_up.tags`, `scale_down.tags`, `pool.tags`, `job_retry.tags`, and the nested SSM tag scopes. Narrower scopes win repeated keys. Queue tags also apply to the configuration build queue and dead-letter queue owned by multi-runner. Stable v1 configurations keep their existing tag behavior unchanged. -The output contracts are separated so a single map never contains two incompatible entry schemas. In v1 mode, entries remain exclusively in `runners_map` with their existing flat shape and `runners_map_v2` is empty. In v2 mode, entries are exposed exclusively through `runners_map_v2`, grouped under `runner`, `scale_up`, `scale_down`, `pool`, and `provider`, while `runners_map` is empty. Use `runners_map_v2["configuration"].runner.role` for the common runner role and `runners_map_v2["configuration"].scale_up.lambda`, `.log_group`, and `.role` for the scale-up resources. The same resource shape is used for `scale_down` and an enabled `pool`; `pool` is null when it is disabled. Provider-specific resources are grouped under the selected provider key, which also identifies the compute provider. For EC2, launch-template and runner-log resources remain under `runners_map_v2["configuration"].provider.ec2`. +The output contracts are separated so a single map never contains two incompatible entry schemas. In v1 mode, entries remain exclusively in `runners_map` with their existing flat shape and `runners_map_v2` is empty. In v2 mode, entries are exposed exclusively through `runners_map_v2`, grouped under `runner`, `scale_up`, `scale_down`, `pool`, and `provider`, while `runners_map` is empty. Use `runners_map_v2["configuration"].runner.role` for the common runner role and `runners_map_v2["configuration"].scale_up.lambda`, `.log_group`, and `.role` for the scale-up resources. The same resource shape is used for `scale_down` and an enabled `pool`; `pool` is null when it is disabled. Provider-specific resources are grouped under the selected provider key, which also identifies the compute provider. For EC2, launch-template and runner-log resources remain under `runners_map_v2["configuration"].provider.ec2`. For MicroVM, the selected image and execution-role reference are exposed under `runners_map_v2["configuration"].provider.microvm`. ### Multi-runner v2 migration roadmap @@ -174,7 +174,7 @@ module "multi-runner" { | [enable\_ami\_housekeeper](#input\_enable\_ami\_housekeeper) | Option to disable the lambda to clean up old AMIs. | `bool` | `false` | no | | [enable\_managed\_runner\_security\_group](#input\_enable\_managed\_runner\_security\_group) | Enabling the default managed security group creation. Unmanaged security groups can be specified via `runner_additional_security_group_ids`. | `bool` | `true` | no | | [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. |
object({
enable = optional(bool, true)
accept_events = optional(list(string), [])
})
| `{}` | no | -| [experimental](#input\_experimental) | Opt-in experimental features. Omit this object to retain only the stable `multi_runner_config` behavior. Experimental schemas can change before they become stable.

- `multi_runner_config_v2`: Provider-oriented runner configurations keyed by configuration name. To opt into v2, leave `multi_runner_config` empty and populate this map. When this map is empty, stable `multi_runner_config` entries continue to use the unchanged `runners` module. Populating both maps in the same module instance is unsupported.

Each `multi_runner_config_v2` entry supports the following nested fields:

- `tags`: Configuration-wide tags. These override module-level `tags`; narrower component and compute-provider tag maps take precedence for their resources.
- `runner.os`: Runner operating system.
- `runner.architecture`: Runner distribution architecture.
- `runner.boot_time_in_minutes`: Expected boot duration used before a runner is considered stale.
- `runner.disable_default_labels`: Prevents GitHub default labels from being registered.
- `runner.extra_labels`: Additional labels combined with `matcherConfig.labelMatchers`. Default self-hosted, operating-system, and architecture labels are also included unless `runner.disable_default_labels` is true.
- `runner.group_name`: GitHub runner group used during registration.
- `runner.name_prefix`: Prefix added to registered runner names.
- `runner.run_as_root`: Runs the runner service as root when supported by the compute provider.
- `runner.run_as`: Operating-system user used when `run_as_root` is false.
- `runner.maximum_count`: Maximum number of runners for this configuration.
- `runner.ephemeral`: Registers runners in ephemeral mode.
- `runner.jit_config_enabled`: Explicitly enables or disables just-in-time configuration. Null follows `ephemeral`.
- `runner.auto_update_disabled`: Disables the GitHub runner application's built-in updater.
- `runner.tags`: Tags for common runner resources, currently the managed runner IAM role. These override entry-level `tags`.
- `runner.hooks.job_started`: Script content installed as the runner job-started hook.
- `runner.hooks.job_completed`: Script content installed as the runner job-completed hook.
- `runner.iam.role.arn`: ARN of an externally managed runner role. When set, `runner-stack` does not create or modify that role.
- `runner.iam.managed_policy_arns`: Named managed-policy ARNs attached to the module-managed runner role.
- `runner.iam.additional_trust_policy_json`: Optional IAM policy document merged with the selected compute provider's default runner-role trust policy.
- `runner.iam.path`: IAM path for the module-managed runner role.
- `runner.iam.permissions_boundary`: Permissions-boundary ARN for the module-managed runner role.
- `github.organization_runners`: Registers runners at organization scope when true; otherwise repository-scoped registration is used.
- `lambda.tags`: Shared tags for control-plane Lambda functions. Component tags override this map.
- `queue.delay_webhook_event`: Delay in seconds applied to webhook job messages.
- `queue.job_queue_retention_in_seconds`: Build-queue message retention period in seconds.
- `queue.event_source_mapping.batch_size`: Maximum build-queue records delivered to one scale-up Lambda invocation. Null uses the module-level setting.
- `queue.event_source_mapping.maximum_batching_window_in_seconds`: Maximum batching window for build-queue records. Null uses the module-level setting.
- `queue.redrive_build_queue.enabled`: Creates and attaches a dead-letter queue for the build queue.
- `queue.redrive_build_queue.maxReceiveCount`: Number of receives before a build message moves to the dead-letter queue.
- `queue.tags`: Tags for configuration-owned queue resources. These override entry-level `tags`; component tags override this map.
- `scale_up.reserved_concurrent_executions`: Reserved concurrency for the scale-up Lambda. Use `-1` for unreserved concurrency.
- `scale_up.job_queued_check_enabled`: Enables the queued-job verification before scaling. Null follows the runner mode default.
- `scale_up.tags`: Tags for scale-up resources. These override entry-level and shared Lambda, queue, and log-group tags within their resource scopes.
- `scale_down.schedule_expression`: EventBridge schedule expression that invokes scale-down.
- `scale_down.minimum_running_time_in_minutes`: Minimum runner age before scale-down may terminate it. Null selects the operating-system default.
- `scale_down.tags`: Tags for scale-down resources. These override entry-level and shared Lambda and log-group tags within their resource scopes.
- `scale_down.idle_config`: Time-based desired idle-runner configurations.
- `scale_down.idle_config[].cron`: Cron expression identifying when the idle configuration applies.
- `scale_down.idle_config[].timeZone`: IANA time zone used to evaluate `cron`.
- `scale_down.idle_config[].idleCount`: Number of idle runners to retain during the matching period.
- `scale_down.idle_config[].evictionStrategy`: Selection strategy used when excess idle runners are removed.
- `pool.config`: Scheduled target pool sizes. An empty list disables the pool component.
- `pool.config[].schedule_expression`: Scheduler expression that activates the target size.
- `pool.config[].schedule_expression_timezone`: Optional IANA time zone used to evaluate the schedule.
- `pool.config[].size`: Desired number of runners for the schedule.
- `pool.runner_owner`: Optional GitHub organization or repository owner used when creating pooled runners.
- `pool.tags`: Tags for pool resources. These override entry-level and shared Lambda and log-group tags within their resource scopes.
- `job_retry.enabled`: Creates the retry queue, Lambda function, event-source mapping, and related IAM resources.
- `job_retry.delay_in_seconds`: Initial delay before a queued-job retry check.
- `job_retry.delay_backoff`: Multiplier applied to the delay after each unsuccessful check.
- `job_retry.max_attempts`: Maximum retry-check attempts before the message is no longer republished.
- `job_retry.tags`: Tags for job-retry resources. These override entry-level and shared Lambda, queue, and log-group tags within their resource scopes.
- `job_retry.lambda.memory_size`: Memory allocated to the job-retry Lambda in MB.
- `job_retry.lambda.reserved_concurrent_executions`: Reserved concurrency for the job-retry Lambda. Use `-1` for unreserved concurrency.
- `job_retry.lambda.timeout`: Job-retry Lambda timeout in seconds and visibility timeout for its retry queue.
- `ssm.tags`: Shared tags for SSM-related resources. These override entry-level `tags`.
- `ssm.kms_key`: Optional customer-managed KMS key used for temporary registration parameters. The wrapper's presence selects the KMS policy at plan time.
- `ssm.kms_key.arn`: ARN of the customer-managed KMS key. The ARN may be unknown until apply.
- `ssm.parameters.tags`: Tags for Terraform-managed and runtime-created runner configuration parameters. These override `ssm.tags`.
- `ssm.housekeeper.tags`: Tags for SSM housekeeper resources. These override entry-level, shared Lambda, shared log, and `ssm.tags` values.
- `observability.logs.tags`: Shared tags for CloudWatch log groups. Component tags override this map.
- `compute_provider`: Typed compute-provider blocks. Exactly one block must be non-null, and the populated block selects the provider. Its presence must be known during planning; values inside it may remain unknown until apply.
- `compute_provider.ec2`: EC2-specific configuration.
- `compute_provider.ec2.ami.filter`: EC2 AMI filters combined with the default AMI-name filter.
- `compute_provider.ec2.ami.owners`: AWS account IDs or aliases allowed to own the selected AMI.
- `compute_provider.ec2.ami.id_ssm_parameter`: Optional externally managed SSM parameter containing the AMI ID. The wrapper's presence selects external ownership at plan time.
- `compute_provider.ec2.ami.id_ssm_parameter.arn`: ARN of the externally managed SSM parameter. The ARN may be unknown until apply.
- `compute_provider.ec2.ami.kms_key`: Optional KMS key required to launch encrypted AMIs or snapshots. The wrapper's presence selects the KMS policy at plan time.
- `compute_provider.ec2.ami.kms_key.arn`: ARN of the KMS key. The ARN may be unknown until apply.
- `compute_provider.ec2.block_device_mappings`: EBS mappings added to the runner launch template.
- `compute_provider.ec2.block_device_mappings[].delete_on_termination`: Deletes the volume when its runner instance terminates.
- `compute_provider.ec2.block_device_mappings[].device_name`: Device name exposed to the runner instance.
- `compute_provider.ec2.block_device_mappings[].encrypted`: Enables EBS encryption.
- `compute_provider.ec2.block_device_mappings[].iops`: Provisioned IOPS for supported volume types.
- `compute_provider.ec2.block_device_mappings[].kms_key_id`: KMS key ID or ARN used to encrypt the volume.
- `compute_provider.ec2.block_device_mappings[].snapshot_id`: Snapshot used to initialize the volume.
- `compute_provider.ec2.block_device_mappings[].throughput`: Provisioned throughput for supported volume types.
- `compute_provider.ec2.block_device_mappings[].volume_initialization_rate`: Fixed initialization rate in MiB/s for supported snapshot-backed volumes.
- `compute_provider.ec2.block_device_mappings[].volume_size`: Volume size in GiB.
- `compute_provider.ec2.block_device_mappings[].volume_type`: EBS volume type.
- `compute_provider.ec2.create_service_linked_role_spot`: Allows scale-up to create the EC2 Spot service-linked role.
- `compute_provider.ec2.credit_specification`: CPU credit mode for burstable instance types.
- `compute_provider.ec2.ebs_optimized`: Requests EBS-optimized runner instances.
- `compute_provider.ec2.cloudwatch_agent.enabled`: Installs and configures the CloudWatch agent through the default bootstrap flow.
- `compute_provider.ec2.cloudwatch_agent.config`: Optional complete CloudWatch agent configuration.
- `compute_provider.ec2.binaries_syncer.enabled`: Enables use of the module-level synchronized runner distribution from S3.
- `compute_provider.ec2.detailed_monitoring_enabled`: Enables detailed EC2 monitoring for runner instances.
- `compute_provider.ec2.ssm_enabled`: Attaches runner permissions and policies required for AWS Systems Manager access.
- `compute_provider.ec2.user_data.enabled`: Enables launch-template user data.
- `compute_provider.ec2.user_data.template`: Optional path to a custom user-data template.
- `compute_provider.ec2.user_data.content`: Optional complete user-data content used instead of rendering a template.
- `compute_provider.ec2.user_data.pre_install`: Script content inserted before runner installation in the default template.
- `compute_provider.ec2.user_data.post_install`: Script content inserted after runner installation in the default template.
- `compute_provider.ec2.user_data.debug_logging_enabled`: Enables verbose user-data tracing, which can expose secrets in logs.
- `compute_provider.ec2.instance_allocation_strategy`: EC2 Fleet allocation strategy used to select capacity.
- `compute_provider.ec2.instance_max_spot_price`: Optional maximum hourly Spot price.
- `compute_provider.ec2.instance_target_capacity_type`: Primary capacity type, either `spot` or `on-demand`.
- `compute_provider.ec2.instance_type_priorities`: Optional numeric priorities keyed by instance type.
- `compute_provider.ec2.instance_types`: EC2 instance types available to the scale-up and pool functions.
- `compute_provider.ec2.additional_security_group_ids`: Existing security groups attached to runner instances.
- `compute_provider.ec2.instance_profile.name`: Name of an externally managed instance profile. Setting it also requires `runner.iam.role`.
- `compute_provider.ec2.enable_on_demand_failover_for_errors`: EC2 error codes that trigger an on-demand fallback after a Spot launch failure.
- `compute_provider.ec2.scale_errors`: EC2 error codes treated as retryable scale-up failures.
- `compute_provider.ec2.subnet_ids`: Subnets from which scale-up may launch runners. Null uses the module-level value.
- `compute_provider.ec2.vpc_id`: VPC in which runner networking resources are created. Null uses the module-level value.
- `compute_provider.ec2.cpu_options.core_count`: Number of CPU cores exposed to the runner instance.
- `compute_provider.ec2.cpu_options.threads_per_core`: Number of hardware threads exposed per CPU core.
- `compute_provider.ec2.cpu_options.amd_sev_snp`: Enables or disables AMD SEV-SNP on supported instance types.
- `compute_provider.ec2.cpu_options.nested_virtualization`: Enables or disables nested virtualization on supported instance types.
- `compute_provider.ec2.placement.affinity`: Host affinity setting.
- `compute_provider.ec2.placement.availability_zone`: Availability Zone in which the instance is placed.
- `compute_provider.ec2.placement.group_id`: Placement-group ID.
- `compute_provider.ec2.placement.group_name`: Placement-group name.
- `compute_provider.ec2.placement.host_id`: Dedicated Host ID.
- `compute_provider.ec2.placement.host_resource_group_arn`: ARN of the host resource group used for placement.
- `compute_provider.ec2.placement.spread_domain`: Spread-domain placement value.
- `compute_provider.ec2.placement.tenancy`: Instance tenancy.
- `compute_provider.ec2.placement.partition_number`: Placement-group partition number.
- `compute_provider.ec2.license_specifications[].license_configuration_arn`: ARN of a License Manager license configuration.
- `compute_provider.ec2.use_dedicated_host`: Enables the dedicated-host launch path required for macOS runners.
- `compute_provider.ec2.log_files`: Optional log files collected by the CloudWatch agent.
- `compute_provider.ec2.log_files[].log_group_name`: CloudWatch log-group name before optional prefixing.
- `compute_provider.ec2.log_files[].prefix_log_group`: Prefixes the log-group name with the runner stack path when true.
- `compute_provider.ec2.log_files[].file_path`: File or glob read by the CloudWatch agent.
- `compute_provider.ec2.log_files[].log_stream_name`: CloudWatch log-stream name template.
- `compute_provider.ec2.log_files[].log_class`: CloudWatch log-group class for the collected file.
- `compute_provider.ec2.tags`: Tags for runtime EC2 instances, volumes, network interfaces, and eligible Spot requests. These override entry-level tags and the generated runner `Name`; provider-required bootstrap tags take final precedence.
- `compute_provider.ec2.metadata_options.instance_metadata_tags`: Exposes instance tags through Instance Metadata Service when enabled.
- `compute_provider.ec2.metadata_options.http_endpoint`: Enables or disables the Instance Metadata Service endpoint.
- `compute_provider.ec2.metadata_options.http_tokens`: Controls whether IMDSv2 session tokens are optional or required.
- `compute_provider.ec2.metadata_options.http_put_response_hop_limit`: Network hop limit for Instance Metadata Service token responses.
- `matcherConfig.labelMatchers`: Groups of labels used to match webhook jobs to this configuration.
- `matcherConfig.exactMatch`: Requires the job labels to exactly match a configured label group.
- `matcherConfig.bidirectionalLabelMatch`: Requires labels to match in both directions instead of allowing configured subsets.
- `matcherConfig.priority`: Ordering used when multiple configurations match the same job.
- `matcherConfig.enableDynamicLabels`: Enables runtime interpretation of supported dynamic AWS labels.
- `matcherConfig.awsDynamicLabelsPolicy`: Optional policy restricting values accepted from dynamic AWS labels. |
object({
multi_runner_config_v2 = optional(map(object({
tags = optional(map(string), {})

runner = object({
os = string
architecture = string
boot_time_in_minutes = optional(number, 5)
disable_default_labels = optional(bool, false)
extra_labels = optional(list(string), [])
group_name = optional(string, "Default")
name_prefix = optional(string, "")
run_as_root = optional(bool, false)
run_as = optional(string, "ec2-user")
maximum_count = number
ephemeral = optional(bool, false)
jit_config_enabled = optional(bool, null)
auto_update_disabled = optional(bool, false)
tags = optional(map(string), {})
hooks = optional(object({
job_started = optional(string, "")
job_completed = optional(string, "")
}), {})
iam = optional(object({
role = optional(object({
arn = string
}), null)
managed_policy_arns = optional(map(string), {})
additional_trust_policy_json = optional(string, null)
path = optional(string, null)
permissions_boundary = optional(string, null)
}), {})
})

github = optional(object({
organization_runners = optional(bool, false)
}), {})

lambda = optional(object({
tags = optional(map(string), {})
}), {})

queue = optional(object({
delay_webhook_event = optional(number, 30)
job_queue_retention_in_seconds = optional(number, 86400)
event_source_mapping = optional(object({
batch_size = optional(number, null)
maximum_batching_window_in_seconds = optional(number, null)
}), {})
redrive_build_queue = optional(object({
enabled = bool
maxReceiveCount = number
}), {
enabled = false
maxReceiveCount = null
})
tags = optional(map(string), {})
}), {})

scale_up = optional(object({
reserved_concurrent_executions = optional(number, 1)
job_queued_check_enabled = optional(bool, null)
tags = optional(map(string), {})
}), {})

scale_down = optional(object({
schedule_expression = optional(string, "cron(*/5 * * * ? *)")
minimum_running_time_in_minutes = optional(number, null)
tags = optional(map(string), {})
idle_config = optional(list(object({
cron = string
timeZone = string
idleCount = number
evictionStrategy = optional(string, "oldest_first")
})), [])
}), {})

pool = optional(object({
config = optional(list(object({
schedule_expression = string
schedule_expression_timezone = optional(string)
size = number
})), [])
runner_owner = optional(string, null)
tags = optional(map(string), {})
}), {})

job_retry = optional(object({
enabled = optional(bool, false)
delay_in_seconds = optional(number, 300)
delay_backoff = optional(number, 2)
max_attempts = optional(number, 1)
tags = optional(map(string), {})
lambda = optional(object({
memory_size = optional(number, 256)
reserved_concurrent_executions = optional(number, 1)
timeout = optional(number, 30)
}), {})
}), {})

ssm = optional(object({
tags = optional(map(string), {})
kms_key = optional(object({
arn = string
}), null)
parameters = optional(object({
tags = optional(map(string), {})
}), {})
housekeeper = optional(object({
tags = optional(map(string), {})
}), {})
}), {})

observability = optional(object({
logs = optional(object({
tags = optional(map(string), {})
}), {})
}), {})

compute_provider = object({
ec2 = optional(object({
metadata_options = optional(object({
instance_metadata_tags = optional(string, "enabled")
http_endpoint = optional(string, "enabled")
http_tokens = optional(string, "required")
http_put_response_hop_limit = optional(number, 1)
}), {})
ami = optional(object({
filter = optional(map(list(string)), { state = ["available"] })
owners = optional(list(string), ["amazon"])
id_ssm_parameter = optional(object({
arn = string
}), null)
kms_key = optional(object({
arn = string
}), null)
}), null)
block_device_mappings = optional(list(object({
delete_on_termination = optional(bool, true)
device_name = optional(string, "/dev/xvda")
encrypted = optional(bool, true)
iops = optional(number)
kms_key_id = optional(string)
snapshot_id = optional(string)
throughput = optional(number)
volume_initialization_rate = optional(number)
volume_size = number
volume_type = optional(string, "gp3")
})), [{
volume_size = 30
}])
create_service_linked_role_spot = optional(bool, false)
credit_specification = optional(string, null)
ebs_optimized = optional(bool, false)
cloudwatch_agent = optional(object({
enabled = optional(bool, true)
config = optional(string, null)
}), {})
binaries_syncer = optional(object({
enabled = optional(bool, true)
}), {})
detailed_monitoring_enabled = optional(bool, false)
ssm_enabled = optional(bool, false)
user_data = optional(object({
enabled = optional(bool, true)
template = optional(string, null)
content = optional(string, null)
pre_install = optional(string, "")
post_install = optional(string, "")
debug_logging_enabled = optional(bool, false)
}), {})
instance_allocation_strategy = optional(string, "lowest-price")
instance_max_spot_price = optional(string, null)
instance_target_capacity_type = optional(string, "spot")
instance_type_priorities = optional(map(number), null)
instance_types = list(string)
additional_security_group_ids = optional(list(string), [])
instance_profile = optional(object({
name = string
}), null)
enable_on_demand_failover_for_errors = optional(list(string), [])
scale_errors = optional(list(string), [
"UnfulfillableCapacity",
"MaxSpotInstanceCountExceeded",
"TargetCapacityLimitExceededException",
"RequestLimitExceeded",
"ResourceLimitExceeded",
"MaxSpotInstanceCountExceeded",
"MaxSpotFleetRequestCountExceeded",
"InsufficientInstanceCapacity",
"InsufficientCapacityOnHost",
])
subnet_ids = optional(list(string), null)
vpc_id = optional(string, null)
cpu_options = optional(object({
core_count = optional(number)
threads_per_core = optional(number)
amd_sev_snp = optional(string)
nested_virtualization = optional(string)
}), null)
placement = optional(object({
affinity = optional(string)
availability_zone = optional(string)
group_id = optional(string)
group_name = optional(string)
host_id = optional(string)
host_resource_group_arn = optional(string)
spread_domain = optional(string)
tenancy = optional(string)
partition_number = optional(number)
}), null)
license_specifications = optional(list(object({
license_configuration_arn = string
})), [])
use_dedicated_host = optional(bool, false)
log_files = optional(list(object({
log_group_name = string
prefix_log_group = bool
file_path = string
log_stream_name = string
log_class = optional(string, "STANDARD")
})), null)
tags = optional(map(string), {})
}), null)
})

matcherConfig = object({
labelMatchers = list(list(string))
exactMatch = optional(bool, false)
bidirectionalLabelMatch = optional(bool, false)
priority = optional(number, 999)
enableDynamicLabels = optional(bool, false)
awsDynamicLabelsPolicy = optional(object({
blocked_keys = optional(list(string), [])
restricted_keys = optional(map(object({
allowed = optional(list(string), [])
denied = optional(list(string), [])
max = optional(string, null)
})), {})
}), null)
})
})), {})
})
| `{}` | no | +| [experimental](#input\_experimental) | Opt-in experimental features. Omit this object to retain only the stable `multi_runner_config` behavior. Experimental schemas can change before they become stable.

- `multi_runner_config_v2`: Provider-oriented runner configurations keyed by configuration name. To opt into v2, leave `multi_runner_config` empty and populate this map. When this map is empty, stable `multi_runner_config` entries continue to use the unchanged `runners` module. Populating both maps in the same module instance is unsupported.

Each `multi_runner_config_v2` entry supports the following nested fields:

- `tags`: Configuration-wide tags. These override module-level `tags`; narrower component and compute-provider tag maps take precedence for their resources.
- `runner.os`: Runner operating system.
- `runner.architecture`: Runner distribution architecture.
- `runner.boot_time_in_minutes`: Expected boot duration used before a runner is considered stale.
- `runner.disable_default_labels`: Prevents GitHub default labels from being registered.
- `runner.extra_labels`: Additional labels combined with `matcherConfig.labelMatchers`. Default self-hosted, operating-system, and architecture labels are also included unless `runner.disable_default_labels` is true.
- `runner.group_name`: GitHub runner group used during registration.
- `runner.name_prefix`: Prefix added to registered runner names.
- `runner.run_as_root`: Runs the runner service as root when supported by the compute provider.
- `runner.run_as`: Operating-system user used when `run_as_root` is false.
- `runner.maximum_count`: Maximum number of runners for this configuration.
- `runner.ephemeral`: Registers runners in ephemeral mode.
- `runner.jit_config_enabled`: Explicitly enables or disables just-in-time configuration. Null follows `ephemeral`.
- `runner.auto_update_disabled`: Disables the GitHub runner application's built-in updater.
- `runner.tags`: Tags for common runner resources, currently the managed runner IAM role. These override entry-level `tags`.
- `runner.hooks.job_started`: Script content installed as the runner job-started hook.
- `runner.hooks.job_completed`: Script content installed as the runner job-completed hook.
- `runner.iam.role.arn`: ARN of an externally managed runner role. When set, `runner-stack` does not create or modify that role.
- `runner.iam.managed_policy_arns`: Named managed-policy ARNs attached to the module-managed runner role.
- `runner.iam.additional_trust_policy_json`: Optional IAM policy document merged with the selected compute provider's default runner-role trust policy.
- `runner.iam.path`: IAM path for the module-managed runner role.
- `runner.iam.permissions_boundary`: Permissions-boundary ARN for the module-managed runner role.
- `github.organization_runners`: Registers runners at organization scope when true; otherwise repository-scoped registration is used.
- `lambda.tags`: Shared tags for control-plane Lambda functions. Component tags override this map.
- `queue.delay_webhook_event`: Delay in seconds applied to webhook job messages.
- `queue.job_queue_retention_in_seconds`: Build-queue message retention period in seconds.
- `queue.event_source_mapping.batch_size`: Maximum build-queue records delivered to one scale-up Lambda invocation. Null uses the module-level setting.
- `queue.event_source_mapping.maximum_batching_window_in_seconds`: Maximum batching window for build-queue records. Null uses the module-level setting.
- `queue.redrive_build_queue.enabled`: Creates and attaches a dead-letter queue for the build queue.
- `queue.redrive_build_queue.maxReceiveCount`: Number of receives before a build message moves to the dead-letter queue.
- `queue.tags`: Tags for configuration-owned queue resources. These override entry-level `tags`; component tags override this map.
- `scale_up.reserved_concurrent_executions`: Reserved concurrency for the scale-up Lambda. Use `-1` for unreserved concurrency.
- `scale_up.job_queued_check_enabled`: Enables the queued-job verification before scaling. Null follows the runner mode default.
- `scale_up.tags`: Tags for scale-up resources. These override entry-level and shared Lambda, queue, and log-group tags within their resource scopes.
- `scale_down.schedule_expression`: EventBridge schedule expression that invokes scale-down.
- `scale_down.minimum_running_time_in_minutes`: Minimum runner age before scale-down may terminate it. Null selects the operating-system default.
- `scale_down.tags`: Tags for scale-down resources. These override entry-level and shared Lambda and log-group tags within their resource scopes.
- `scale_down.idle_config`: Time-based desired idle-runner configurations.
- `scale_down.idle_config[].cron`: Cron expression identifying when the idle configuration applies.
- `scale_down.idle_config[].timeZone`: IANA time zone used to evaluate `cron`.
- `scale_down.idle_config[].idleCount`: Number of idle runners to retain during the matching period.
- `scale_down.idle_config[].evictionStrategy`: Selection strategy used when excess idle runners are removed.
- `pool.config`: Scheduled target pool sizes. An empty list disables the pool component.
- `pool.config[].schedule_expression`: Scheduler expression that activates the target size.
- `pool.config[].schedule_expression_timezone`: Optional IANA time zone used to evaluate the schedule.
- `pool.config[].size`: Desired number of runners for the schedule.
- `pool.runner_owner`: Optional GitHub organization or repository owner used when creating pooled runners.
- `pool.tags`: Tags for pool resources. These override entry-level and shared Lambda and log-group tags within their resource scopes.
- `job_retry.enabled`: Creates the retry queue, Lambda function, event-source mapping, and related IAM resources.
- `job_retry.delay_in_seconds`: Initial delay before a queued-job retry check.
- `job_retry.delay_backoff`: Multiplier applied to the delay after each unsuccessful check.
- `job_retry.max_attempts`: Maximum retry-check attempts before the message is no longer republished.
- `job_retry.tags`: Tags for job-retry resources. These override entry-level and shared Lambda, queue, and log-group tags within their resource scopes.
- `job_retry.lambda.memory_size`: Memory allocated to the job-retry Lambda in MB.
- `job_retry.lambda.reserved_concurrent_executions`: Reserved concurrency for the job-retry Lambda. Use `-1` for unreserved concurrency.
- `job_retry.lambda.timeout`: Job-retry Lambda timeout in seconds and visibility timeout for its retry queue.
- `ssm.tags`: Shared tags for SSM-related resources. These override entry-level `tags`.
- `ssm.kms_key`: Optional customer-managed KMS key used for temporary registration parameters. The wrapper's presence selects the KMS policy at plan time.
- `ssm.kms_key.arn`: ARN of the customer-managed KMS key. The ARN may be unknown until apply.
- `ssm.parameters.tags`: Tags for Terraform-managed and runtime-created runner configuration parameters. These override `ssm.tags`.
- `ssm.housekeeper.tags`: Tags for SSM housekeeper resources. These override entry-level, shared Lambda, shared log, and `ssm.tags` values.
- `observability.logs.tags`: Shared tags for CloudWatch log groups. Component tags override this map.
- `compute_provider`: Typed compute-provider blocks. Exactly one block must be non-null, and the populated block selects the provider. Its presence must be known during planning; values inside it may remain unknown until apply.
- `compute_provider.ec2`: EC2-specific configuration.
- `compute_provider.ec2.ami.filter`: EC2 AMI filters combined with the default AMI-name filter.
- `compute_provider.ec2.ami.owners`: AWS account IDs or aliases allowed to own the selected AMI.
- `compute_provider.ec2.ami.id_ssm_parameter`: Optional externally managed SSM parameter containing the AMI ID. The wrapper's presence selects external ownership at plan time.
- `compute_provider.ec2.ami.id_ssm_parameter.arn`: ARN of the externally managed SSM parameter. The ARN may be unknown until apply.
- `compute_provider.ec2.ami.kms_key`: Optional KMS key required to launch encrypted AMIs or snapshots. The wrapper's presence selects the KMS policy at plan time.
- `compute_provider.ec2.ami.kms_key.arn`: ARN of the KMS key. The ARN may be unknown until apply.
- `compute_provider.ec2.block_device_mappings`: EBS mappings added to the runner launch template.
- `compute_provider.ec2.block_device_mappings[].delete_on_termination`: Deletes the volume when its runner instance terminates.
- `compute_provider.ec2.block_device_mappings[].device_name`: Device name exposed to the runner instance.
- `compute_provider.ec2.block_device_mappings[].encrypted`: Enables EBS encryption.
- `compute_provider.ec2.block_device_mappings[].iops`: Provisioned IOPS for supported volume types.
- `compute_provider.ec2.block_device_mappings[].kms_key_id`: KMS key ID or ARN used to encrypt the volume.
- `compute_provider.ec2.block_device_mappings[].snapshot_id`: Snapshot used to initialize the volume.
- `compute_provider.ec2.block_device_mappings[].throughput`: Provisioned throughput for supported volume types.
- `compute_provider.ec2.block_device_mappings[].volume_initialization_rate`: Fixed initialization rate in MiB/s for supported snapshot-backed volumes.
- `compute_provider.ec2.block_device_mappings[].volume_size`: Volume size in GiB.
- `compute_provider.ec2.block_device_mappings[].volume_type`: EBS volume type.
- `compute_provider.ec2.create_service_linked_role_spot`: Allows scale-up to create the EC2 Spot service-linked role.
- `compute_provider.ec2.credit_specification`: CPU credit mode for burstable instance types.
- `compute_provider.ec2.ebs_optimized`: Requests EBS-optimized runner instances.
- `compute_provider.ec2.cloudwatch_agent.enabled`: Installs and configures the CloudWatch agent through the default bootstrap flow.
- `compute_provider.ec2.cloudwatch_agent.config`: Optional complete CloudWatch agent configuration.
- `compute_provider.ec2.binaries_syncer.enabled`: Enables use of the module-level synchronized runner distribution from S3.
- `compute_provider.ec2.detailed_monitoring_enabled`: Enables detailed EC2 monitoring for runner instances.
- `compute_provider.ec2.ssm_enabled`: Attaches runner permissions and policies required for AWS Systems Manager access.
- `compute_provider.ec2.user_data.enabled`: Enables launch-template user data.
- `compute_provider.ec2.user_data.template`: Optional path to a custom user-data template.
- `compute_provider.ec2.user_data.content`: Optional complete user-data content used instead of rendering a template.
- `compute_provider.ec2.user_data.pre_install`: Script content inserted before runner installation in the default template.
- `compute_provider.ec2.user_data.post_install`: Script content inserted after runner installation in the default template.
- `compute_provider.ec2.user_data.debug_logging_enabled`: Enables verbose user-data tracing, which can expose secrets in logs.
- `compute_provider.ec2.instance_allocation_strategy`: EC2 Fleet allocation strategy used to select capacity.
- `compute_provider.ec2.instance_max_spot_price`: Optional maximum hourly Spot price.
- `compute_provider.ec2.instance_target_capacity_type`: Primary capacity type, either `spot` or `on-demand`.
- `compute_provider.ec2.instance_type_priorities`: Optional numeric priorities keyed by instance type.
- `compute_provider.ec2.instance_types`: EC2 instance types available to the scale-up and pool functions.
- `compute_provider.ec2.additional_security_group_ids`: Existing security groups attached to runner instances.
- `compute_provider.ec2.instance_profile.name`: Name of an externally managed instance profile. Setting it also requires `runner.iam.role`.
- `compute_provider.ec2.enable_on_demand_failover_for_errors`: EC2 error codes that trigger an on-demand fallback after a Spot launch failure.
- `compute_provider.ec2.scale_errors`: EC2 error codes treated as retryable scale-up failures.
- `compute_provider.ec2.subnet_ids`: Subnets from which scale-up may launch runners. Null uses the module-level value.
- `compute_provider.ec2.vpc_id`: VPC in which runner networking resources are created. Null uses the module-level value.
- `compute_provider.ec2.cpu_options.core_count`: Number of CPU cores exposed to the runner instance.
- `compute_provider.ec2.cpu_options.threads_per_core`: Number of hardware threads exposed per CPU core.
- `compute_provider.ec2.cpu_options.amd_sev_snp`: Enables or disables AMD SEV-SNP on supported instance types.
- `compute_provider.ec2.cpu_options.nested_virtualization`: Enables or disables nested virtualization on supported instance types.
- `compute_provider.ec2.placement.affinity`: Host affinity setting.
- `compute_provider.ec2.placement.availability_zone`: Availability Zone in which the instance is placed.
- `compute_provider.ec2.placement.group_id`: Placement-group ID.
- `compute_provider.ec2.placement.group_name`: Placement-group name.
- `compute_provider.ec2.placement.host_id`: Dedicated Host ID.
- `compute_provider.ec2.placement.host_resource_group_arn`: ARN of the host resource group used for placement.
- `compute_provider.ec2.placement.spread_domain`: Spread-domain placement value.
- `compute_provider.ec2.placement.tenancy`: Instance tenancy.
- `compute_provider.ec2.placement.partition_number`: Placement-group partition number.
- `compute_provider.ec2.license_specifications[].license_configuration_arn`: ARN of a License Manager license configuration.
- `compute_provider.ec2.use_dedicated_host`: Enables the dedicated-host launch path required for macOS runners.
- `compute_provider.ec2.log_files`: Optional log files collected by the CloudWatch agent.
- `compute_provider.ec2.log_files[].log_group_name`: CloudWatch log-group name before optional prefixing.
- `compute_provider.ec2.log_files[].prefix_log_group`: Prefixes the log-group name with the runner stack path when true.
- `compute_provider.ec2.log_files[].file_path`: File or glob read by the CloudWatch agent.
- `compute_provider.ec2.log_files[].log_stream_name`: CloudWatch log-stream name template.
- `compute_provider.ec2.log_files[].log_class`: CloudWatch log-group class for the collected file.
- `compute_provider.ec2.tags`: Tags for runtime EC2 instances, volumes, network interfaces, and eligible Spot requests. These override entry-level tags and the generated runner `Name`; provider-required bootstrap tags take final precedence.
- `compute_provider.ec2.metadata_options.instance_metadata_tags`: Exposes instance tags through Instance Metadata Service when enabled.
- `compute_provider.ec2.metadata_options.http_endpoint`: Enables or disables the Instance Metadata Service endpoint.
- `compute_provider.ec2.metadata_options.http_tokens`: Controls whether IMDSv2 session tokens are optional or required.
- `compute_provider.ec2.metadata_options.http_put_response_hop_limit`: Network hop limit for Instance Metadata Service token responses.
- `compute_provider.microvm`: Lambda MicroVM-specific configuration.
- `compute_provider.microvm.image_identifier`: ARN or ID of the MicroVM image used to run GitHub runners.
- `compute_provider.microvm.image_version`: Optional MicroVM image version.
- `compute_provider.microvm.execution_role.arn`: Optional externally managed execution role assumed by MicroVMs. Null uses the common runner role.
- `compute_provider.microvm.egress_network_connectors`: Egress network connectors passed to RunMicrovm.
- `compute_provider.microvm.idle_policy`: Optional auto-suspend and auto-resume configuration passed to RunMicrovm.
- `compute_provider.microvm.logging`: Optional RunMicrovm logging union. Exactly one of `cloud_watch` or `disabled` must be selected when set.
- `compute_provider.microvm.run_hook_payload`: Optional payload delivered to the MicroVM `/run` hook. Maximum 16,384 characters.
- `compute_provider.microvm.maximum_duration_in_seconds`: Optional maximum MicroVM lifetime. Valid range is 1 through 28,800 seconds.
- `compute_provider.microvm.environment_variables`: Additional provider-specific Lambda environment variables merged into scale-up, scale-down, and pool.
- `compute_provider.microvm.tags`: Tags encoded into the MicroVM runner configuration.
- `compute_provider.microvm.iam`: Optional MicroVM control-plane IAM overrides and managed policy attachments.
- `matcherConfig.labelMatchers`: Groups of labels used to match webhook jobs to this configuration.
- `matcherConfig.exactMatch`: Requires the job labels to exactly match a configured label group.
- `matcherConfig.bidirectionalLabelMatch`: Requires labels to match in both directions instead of allowing configured subsets.
- `matcherConfig.priority`: Ordering used when multiple configurations match the same job.
- `matcherConfig.enableDynamicLabels`: Enables runtime interpretation of supported dynamic AWS labels.
- `matcherConfig.awsDynamicLabelsPolicy`: Optional policy restricting values accepted from dynamic AWS labels. |
object({
multi_runner_config_v2 = optional(map(object({
tags = optional(map(string), {})

runner = object({
os = string
architecture = string
boot_time_in_minutes = optional(number, 5)
disable_default_labels = optional(bool, false)
extra_labels = optional(list(string), [])
group_name = optional(string, "Default")
name_prefix = optional(string, "")
run_as_root = optional(bool, false)
run_as = optional(string, "ec2-user")
maximum_count = number
ephemeral = optional(bool, false)
jit_config_enabled = optional(bool, null)
auto_update_disabled = optional(bool, false)
tags = optional(map(string), {})
hooks = optional(object({
job_started = optional(string, "")
job_completed = optional(string, "")
}), {})
iam = optional(object({
role = optional(object({
arn = string
}), null)
managed_policy_arns = optional(map(string), {})
additional_trust_policy_json = optional(string, null)
path = optional(string, null)
permissions_boundary = optional(string, null)
}), {})
})

github = optional(object({
organization_runners = optional(bool, false)
}), {})

lambda = optional(object({
tags = optional(map(string), {})
}), {})

queue = optional(object({
delay_webhook_event = optional(number, 30)
job_queue_retention_in_seconds = optional(number, 86400)
event_source_mapping = optional(object({
batch_size = optional(number, null)
maximum_batching_window_in_seconds = optional(number, null)
}), {})
redrive_build_queue = optional(object({
enabled = bool
maxReceiveCount = number
}), {
enabled = false
maxReceiveCount = null
})
tags = optional(map(string), {})
}), {})

scale_up = optional(object({
reserved_concurrent_executions = optional(number, 1)
job_queued_check_enabled = optional(bool, null)
tags = optional(map(string), {})
}), {})

scale_down = optional(object({
schedule_expression = optional(string, "cron(*/5 * * * ? *)")
minimum_running_time_in_minutes = optional(number, null)
tags = optional(map(string), {})
idle_config = optional(list(object({
cron = string
timeZone = string
idleCount = number
evictionStrategy = optional(string, "oldest_first")
})), [])
}), {})

pool = optional(object({
config = optional(list(object({
schedule_expression = string
schedule_expression_timezone = optional(string)
size = number
})), [])
runner_owner = optional(string, null)
tags = optional(map(string), {})
}), {})

job_retry = optional(object({
enabled = optional(bool, false)
delay_in_seconds = optional(number, 300)
delay_backoff = optional(number, 2)
max_attempts = optional(number, 1)
tags = optional(map(string), {})
lambda = optional(object({
memory_size = optional(number, 256)
reserved_concurrent_executions = optional(number, 1)
timeout = optional(number, 30)
}), {})
}), {})

ssm = optional(object({
tags = optional(map(string), {})
kms_key = optional(object({
arn = string
}), null)
parameters = optional(object({
tags = optional(map(string), {})
}), {})
housekeeper = optional(object({
tags = optional(map(string), {})
}), {})
}), {})

observability = optional(object({
logs = optional(object({
tags = optional(map(string), {})
}), {})
}), {})

compute_provider = object({
ec2 = optional(object({
metadata_options = optional(object({
instance_metadata_tags = optional(string, "enabled")
http_endpoint = optional(string, "enabled")
http_tokens = optional(string, "required")
http_put_response_hop_limit = optional(number, 1)
}), {})
ami = optional(object({
filter = optional(map(list(string)), { state = ["available"] })
owners = optional(list(string), ["amazon"])
id_ssm_parameter = optional(object({
arn = string
}), null)
kms_key = optional(object({
arn = string
}), null)
}), null)
block_device_mappings = optional(list(object({
delete_on_termination = optional(bool, true)
device_name = optional(string, "/dev/xvda")
encrypted = optional(bool, true)
iops = optional(number)
kms_key_id = optional(string)
snapshot_id = optional(string)
throughput = optional(number)
volume_initialization_rate = optional(number)
volume_size = number
volume_type = optional(string, "gp3")
})), [{
volume_size = 30
}])
create_service_linked_role_spot = optional(bool, false)
credit_specification = optional(string, null)
ebs_optimized = optional(bool, false)
cloudwatch_agent = optional(object({
enabled = optional(bool, true)
config = optional(string, null)
}), {})
binaries_syncer = optional(object({
enabled = optional(bool, true)
}), {})
detailed_monitoring_enabled = optional(bool, false)
ssm_enabled = optional(bool, false)
user_data = optional(object({
enabled = optional(bool, true)
template = optional(string, null)
content = optional(string, null)
pre_install = optional(string, "")
post_install = optional(string, "")
debug_logging_enabled = optional(bool, false)
}), {})
instance_allocation_strategy = optional(string, "lowest-price")
instance_max_spot_price = optional(string, null)
instance_target_capacity_type = optional(string, "spot")
instance_type_priorities = optional(map(number), null)
instance_types = list(string)
additional_security_group_ids = optional(list(string), [])
instance_profile = optional(object({
name = string
}), null)
enable_on_demand_failover_for_errors = optional(list(string), [])
scale_errors = optional(list(string), [
"UnfulfillableCapacity",
"MaxSpotInstanceCountExceeded",
"TargetCapacityLimitExceededException",
"RequestLimitExceeded",
"ResourceLimitExceeded",
"MaxSpotInstanceCountExceeded",
"MaxSpotFleetRequestCountExceeded",
"InsufficientInstanceCapacity",
"InsufficientCapacityOnHost",
])
subnet_ids = optional(list(string), null)
vpc_id = optional(string, null)
cpu_options = optional(object({
core_count = optional(number)
threads_per_core = optional(number)
amd_sev_snp = optional(string)
nested_virtualization = optional(string)
}), null)
placement = optional(object({
affinity = optional(string)
availability_zone = optional(string)
group_id = optional(string)
group_name = optional(string)
host_id = optional(string)
host_resource_group_arn = optional(string)
spread_domain = optional(string)
tenancy = optional(string)
partition_number = optional(number)
}), null)
license_specifications = optional(list(object({
license_configuration_arn = string
})), [])
use_dedicated_host = optional(bool, false)
log_files = optional(list(object({
log_group_name = string
prefix_log_group = bool
file_path = string
log_stream_name = string
log_class = optional(string, "STANDARD")
})), null)
tags = optional(map(string), {})
}), null)

microvm = optional(object({
image_identifier = string
image_version = optional(string, null)
execution_role = optional(object({
arn = string
}), null)
egress_network_connectors = optional(list(string), [])
idle_policy = optional(object({
max_idle_duration_seconds = number
suspended_duration_seconds = number
auto_resume_enabled = bool
}), null)
logging = optional(object({
cloud_watch = optional(object({
log_group = optional(string, null)
log_stream = optional(string, null)
}), null)
disabled = optional(bool, false)
}), null)
run_hook_payload = optional(string, null)
maximum_duration_in_seconds = optional(number, null)
environment_variables = optional(map(string), {})
tags = optional(map(string), {})
iam = optional(object({
resource_arns = optional(list(string), ["*"])
actions = optional(object({
scale_up = optional(list(string), null)
scale_down = optional(list(string), null)
}), {})
additional_policy_json = optional(object({
scale_up = optional(string, null)
}), {})
managed_policy_arns = optional(object({
scale_up = optional(string, null)
pool = optional(string, null)
}), {})
}), {})
}), null)
})

matcherConfig = object({
labelMatchers = list(list(string))
exactMatch = optional(bool, false)
bidirectionalLabelMatch = optional(bool, false)
priority = optional(number, 999)
enableDynamicLabels = optional(bool, false)
awsDynamicLabelsPolicy = optional(object({
blocked_keys = optional(list(string), [])
restricted_keys = optional(map(object({
allowed = optional(list(string), [])
denied = optional(list(string), [])
max = optional(string, null)
})), {})
}), null)
})
})), {})
})
| `{}` | no | | [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 | | [ghes\_url](#input\_ghes\_url) | GitHub Enterprise Server URL. Example: https://github.internal.co - DO NOT SET IF USING PUBLIC GITHUB. .However if you are using GitHub Enterprise Cloud with data-residency (ghe.com), set the endpoint here. Example - https://companyname.ghe.com\| | `string` | `null` | no | | [github\_app](#input\_github\_app) | GitHub app parameters, see your github app.
You can optionally create the SSM parameters yourself and provide the ARN and name here, through the `*_ssm` attributes.
If you chose to provide the configuration values directly here,
please ensure the key is the base64-encoded `.pem` file (the output of `base64 app.private-key.pem`, not the content of `private-key.pem`).
Note: the provided SSM parameters arn and name have a precedence over the actual value (i.e `key_base64_ssm` has a precedence over `key_base64` etc). |
object({
key_base64 = optional(string)
key_base64_ssm = optional(object({
arn = string
name = string
}))
id = optional(string)
id_ssm = optional(object({
arn = string
name = string
}))
webhook_secret = optional(string)
webhook_secret_ssm = optional(object({
arn = string
name = string
}))
})
| n/a | yes | diff --git a/modules/runner-stack/README.md b/modules/runner-stack/README.md index 1a12714b89..65a8e34bc0 100644 --- a/modules/runner-stack/README.md +++ b/modules/runner-stack/README.md @@ -6,7 +6,7 @@ This internal module implements the experimental provider-neutral runner control The stack coordinates internal modules for [`scale-runners`](./scale-runners), [`pool`](./pool), [`job-retry`](./job-retry), and [`ssm-housekeeper`](./ssm-housekeeper). These modules own their provider-neutral Lambda, scheduler, logging, and IAM resources. The stack also creates or selects the runner IAM role, manages shared runner configuration in SSM, and dispatches the selected compute provider. -Provider-owned settings are typed and nested under the selected provider block; for example, AMI, VPC, instance-profile, capacity, userdata, and runner-host logging settings live under `compute_provider.ec2`. Exactly one provider block must be populated, and its presence must be known during planning; there is no separate input discriminator. Before creating the common runner role, the stack calls the selected provider's isolated `trust-policy` module to combine its default trust with `runner.iam.additional_trust_policy_json`. The resulting assume-role policy does not depend on the full compute-provider module, which receives the resolved runner role only after it is created. EC2 owns the instance profile, launch template, EC2 bootstrap parameters, runner log groups, and its provider policies and Lambda environment variables. The common stack attaches each returned policy group to its runner, scale-up, scale-down, or pool role. The runner-stack output groups provider-specific resources under the matching dynamic provider key, which also identifies the selected provider. EC2 is the only implemented Terraform compute provider in this phase. +Provider-owned settings are typed and nested under the selected provider block; for example, AMI, VPC, instance-profile, capacity, userdata, and runner-host logging settings live under `compute_provider.ec2`, while Lambda MicroVM image and runtime settings live under `compute_provider.microvm`. Exactly one provider block must be populated, and its presence must be known during planning; there is no separate input discriminator. Before creating the common runner role, the stack calls the selected provider's isolated `trust-policy` module to combine its default trust with `runner.iam.additional_trust_policy_json`. The resulting assume-role policy does not depend on the full compute-provider module, which receives the resolved runner role only after it is created. EC2 owns the instance profile, launch template, EC2 bootstrap parameters, runner log groups, and its provider policies and Lambda environment variables. MicroVM owns runtime configuration, execution-role policy, and its provider Lambda environment variables; the runtime Lambdas create and terminate MicroVMs. The common stack attaches each returned policy group to its runner, scale-up, scale-down, or pool role. The runner-stack output groups provider-specific resources under the matching dynamic provider key, which also identifies the selected provider. ## Tagging @@ -80,6 +80,8 @@ yarn run dist | [ec2](#module\_ec2) | ../compute-providers/ec2 | n/a | | [ec2\_trust\_policy](#module\_ec2\_trust\_policy) | ../compute-providers/ec2/trust-policy | n/a | | [job\_retry](#module\_job\_retry) | ./job-retry | n/a | +| [microvm](#module\_microvm) | ../compute-providers/microvm | n/a | +| [microvm\_trust\_policy](#module\_microvm\_trust\_policy) | ../compute-providers/microvm/trust-policy | n/a | | [pool](#module\_pool) | ./pool | n/a | | [scale\_runners](#module\_scale\_runners) | ./scale-runners | n/a | | [ssm\_housekeeper](#module\_ssm\_housekeeper) | ./ssm-housekeeper | n/a | @@ -103,7 +105,7 @@ yarn run dist |------|-------------|------|---------|:--------:| | [aws\_partition](#input\_aws\_partition) | AWS partition used to construct ARNs. | `string` | `"aws"` | no | | [aws\_region](#input\_aws\_region) | AWS region. | `string` | n/a | yes | -| [compute\_provider](#input\_compute\_provider) | Typed compute-provider configuration. Provider-owned settings remain inside the selected compute-provider block.

Exactly one compute-provider block must be non-null. The populated block selects the provider, and its presence must be known during planning. Values inside the selected block may remain unknown until apply.

- `ec2`: EC2 compute-provider configuration.
- `ec2.ami`: Optional AMI discovery or external AMI-parameter configuration. Null uses the operating-system and architecture defaults.
- `ec2.ami.filter`: EC2 AMI filters combined with the provider's default AMI-name filter.
- `ec2.ami.owners`: AWS account IDs or aliases allowed to own the selected AMI.
- `ec2.ami.id_ssm_parameter`: Optional externally managed SSM parameter containing the AMI ID. Null creates a provider-managed AMI-ID parameter. The wrapper's presence is the plan-time ownership discriminator, so keep the object literal even when its ARN comes from another resource.
- `ec2.ami.id_ssm_parameter.arn`: ARN of the externally managed SSM parameter. The ARN may be unknown until apply.
- `ec2.ami.kms_key`: Optional KMS key required to launch encrypted AMIs or snapshots. The wrapper's presence is the plan-time policy discriminator.
- `ec2.ami.kms_key.arn`: ARN of the KMS key. The ARN may be unknown until apply.
- `ec2.vpc_id`: VPC in which runner networking resources are created.
- `ec2.subnet_ids`: Subnets from which scale-up may launch runner instances.
- `ec2.overrides`: Optional resource-name overrides.
- `ec2.overrides.name_runner`: Name tag used for runner compute resources. An empty value uses the generated provider name.
- `ec2.overrides.name_sg`: Name tag used for the managed runner security group. An empty value uses the generated provider name.
- `ec2.instance_profile`: Optional externally managed instance profile used by the launch template.
- `ec2.instance_profile.name`: Name of the externally managed instance profile.
- `ec2.instance_profile_path`: IAM path for the provider-managed instance profile. Null uses a path derived from the stack prefix.
- `ec2.binaries_syncer`: Runner-distribution synchronization configuration.
- `ec2.binaries_syncer.enabled`: Enables use of a synchronized runner distribution from S3.
- `ec2.binaries_syncer.s3`: S3 object containing the synchronized runner distribution. Required when synchronization is enabled.
- `ec2.binaries_syncer.s3.arn`: ARN of the runner-distribution bucket, used by IAM policies.
- `ec2.binaries_syncer.s3.id`: Bucket name used to construct the runner-distribution S3 URI.
- `ec2.binaries_syncer.s3.key`: Object key of the runner distribution.
- `ec2.block_device_mappings`: EBS mappings added to the runner launch template.
- `ec2.block_device_mappings[].delete_on_termination`: Deletes the volume when its runner instance terminates.
- `ec2.block_device_mappings[].device_name`: Device name exposed to the runner instance.
- `ec2.block_device_mappings[].encrypted`: Enables EBS encryption.
- `ec2.block_device_mappings[].iops`: Provisioned IOPS for volume types that support it.
- `ec2.block_device_mappings[].kms_key_id`: KMS key ID or ARN used to encrypt the volume.
- `ec2.block_device_mappings[].snapshot_id`: Snapshot used to initialize the volume.
- `ec2.block_device_mappings[].throughput`: Provisioned throughput for volume types that support it.
- `ec2.block_device_mappings[].volume_initialization_rate`: Fixed initialization rate in MiB/s for supported snapshot-backed volumes.
- `ec2.block_device_mappings[].volume_size`: Volume size in GiB.
- `ec2.block_device_mappings[].volume_type`: EBS volume type.
- `ec2.ebs_optimized`: Requests EBS-optimized runner instances.
- `ec2.instance_target_capacity_type`: Primary capacity type, either `spot` or `on-demand`.
- `ec2.instance_allocation_strategy`: EC2 Fleet allocation strategy used to select instance capacity.
- `ec2.instance_type_priorities`: Optional numeric priorities keyed by instance type.
- `ec2.instance_max_spot_price`: Optional maximum hourly Spot price.
- `ec2.instance_types`: EC2 instance types available to the scale-up and pool functions.
- `ec2.user_data`: Runner bootstrap user-data configuration.
- `ec2.user_data.enabled`: Enables launch-template user data.
- `ec2.user_data.template`: Optional path to a custom user-data template.
- `ec2.user_data.content`: Optional complete user-data content. When set, it is used instead of rendering a template.
- `ec2.user_data.pre_install`: Script content inserted before runner installation in the default template.
- `ec2.user_data.post_install`: Script content inserted after runner installation in the default template.
- `ec2.user_data.debug_logging_enabled`: Enables verbose user-data tracing, which can expose secrets in logs.
- `ec2.ssm_enabled`: Attaches runner permissions and policies required for AWS Systems Manager access.
- `ec2.create_service_linked_role_spot`: Allows scale-up to create the EC2 Spot service-linked role.
- `ec2.cloudwatch_agent`: CloudWatch agent configuration for runner instances.
- `ec2.cloudwatch_agent.enabled`: Installs and configures the CloudWatch agent through the default bootstrap flow.
- `ec2.cloudwatch_agent.config`: Optional complete CloudWatch agent configuration. Null renders the provider default from `log_files`.
- `ec2.managed_security_group_enabled`: Creates and attaches the provider-managed runner security group.
- `ec2.log_files`: Optional log files collected by the CloudWatch agent. Null uses the provider defaults.
- `ec2.log_files[].log_group_name`: CloudWatch log-group name, before optional prefixing.
- `ec2.log_files[].prefix_log_group`: Prefixes the log-group name with the runner stack path when true.
- `ec2.log_files[].file_path`: File or glob read by the CloudWatch agent.
- `ec2.log_files[].log_stream_name`: CloudWatch log-stream name template.
- `ec2.log_files[].log_class`: CloudWatch log-group class for the collected file.
- `ec2.key_name`: Optional EC2 key-pair name added to the launch template.
- `ec2.additional_security_group_ids`: Existing security groups attached in addition to the managed security group.
- `ec2.detailed_monitoring_enabled`: Enables detailed EC2 monitoring for runner instances.
- `ec2.egress_rules`: Egress rules created on the managed runner security group.
- `ec2.egress_rules[].cidr_blocks`: IPv4 CIDR destinations.
- `ec2.egress_rules[].ipv6_cidr_blocks`: IPv6 CIDR destinations.
- `ec2.egress_rules[].prefix_list_ids`: AWS prefix-list destinations.
- `ec2.egress_rules[].from_port`: First destination port in the permitted range.
- `ec2.egress_rules[].protocol`: IP protocol name or number. Use `-1` for all protocols.
- `ec2.egress_rules[].security_groups`: Destination security-group IDs.
- `ec2.egress_rules[].self`: Allows traffic to the managed security group itself when true.
- `ec2.egress_rules[].to_port`: Last destination port in the permitted range.
- `ec2.egress_rules[].description`: Optional rule description.
- `ec2.tags`: Additional tags for runner instances, EBS volumes, network interfaces, and eligible Spot instance requests created from the launch template. They override module-level tags and the generated runner `Name`; the provider-managed `ghr:environment`, `ghr:ssm_config_path`, and `ghr:runner_name_prefix` bootstrap tags take final precedence. These tags do not apply to static provider resources such as the launch template, security group, IAM resources, SSM parameters, or log groups.
- `ec2.metadata_options`: Instance Metadata Service configuration in the launch template.
- `ec2.metadata_options.instance_metadata_tags`: Exposes instance tags through Instance Metadata Service when `enabled`.
- `ec2.metadata_options.http_endpoint`: Enables or disables the Instance Metadata Service endpoint.
- `ec2.metadata_options.http_tokens`: Controls whether IMDSv2 session tokens are optional or required.
- `ec2.metadata_options.http_put_response_hop_limit`: Network hop limit for Instance Metadata Service token responses.
- `ec2.credit_specification`: CPU credit mode for burstable instance types, either `standard` or `unlimited`.
- `ec2.cpu_options`: CPU topology and processor-feature configuration.
- `ec2.cpu_options.core_count`: Number of CPU cores exposed to the runner instance.
- `ec2.cpu_options.threads_per_core`: Number of hardware threads exposed per CPU core.
- `ec2.cpu_options.amd_sev_snp`: Enables or disables AMD SEV-SNP on supported instance types.
- `ec2.cpu_options.nested_virtualization`: Enables or disables nested virtualization on supported instance types.
- `ec2.placement`: EC2 placement configuration for runner instances.
- `ec2.placement.affinity`: Host affinity setting.
- `ec2.placement.availability_zone`: Availability Zone in which the instance is placed.
- `ec2.placement.group_id`: Placement-group ID.
- `ec2.placement.group_name`: Placement-group name.
- `ec2.placement.host_id`: Dedicated Host ID.
- `ec2.placement.host_resource_group_arn`: ARN of the host resource group used for placement.
- `ec2.placement.spread_domain`: Spread-domain placement value.
- `ec2.placement.tenancy`: Instance tenancy, such as `default`, `dedicated`, or `host`.
- `ec2.placement.partition_number`: Placement-group partition number.
- `ec2.license_specifications`: License Manager configurations added to the launch template.
- `ec2.license_specifications[].license_configuration_arn`: ARN of a License Manager license configuration.
- `ec2.associate_public_ipv4_address`: Associates a public IPv4 address with runner network interfaces.
- `ec2.enable_on_demand_failover_for_errors`: EC2 error codes that trigger an on-demand fallback after a Spot launch failure.
- `ec2.scale_errors`: EC2 error codes treated as retryable scale-up failures.
- `ec2.use_dedicated_host`: Enables the dedicated-host launch path, required for macOS runners. |
object({
ec2 = optional(object({
ami = optional(object({
filter = optional(map(list(string)), { state = ["available"] })
owners = optional(list(string), ["amazon"])
id_ssm_parameter = optional(object({
arn = string
}), null)
kms_key = optional(object({
arn = string
}), null)
}), null)
vpc_id = string
subnet_ids = list(string)
overrides = optional(object({
name_runner = optional(string, "")
name_sg = optional(string, "")
}), {})
instance_profile = optional(object({
name = string
}), null)
instance_profile_path = optional(string, null)
binaries_syncer = optional(object({
enabled = optional(bool, true)
s3 = optional(object({
arn = string
id = string
key = string
}), null)
}), {})
block_device_mappings = optional(list(object({
delete_on_termination = optional(bool, true)
device_name = optional(string, "/dev/xvda")
encrypted = optional(bool, true)
iops = optional(number)
kms_key_id = optional(string)
snapshot_id = optional(string)
throughput = optional(number)
volume_initialization_rate = optional(number)
volume_size = number
volume_type = optional(string, "gp3")
})), [{ volume_size = 30 }])
ebs_optimized = optional(bool, false)
instance_target_capacity_type = optional(string, "spot")
instance_allocation_strategy = optional(string, "lowest-price")
instance_type_priorities = optional(map(number), null)
instance_max_spot_price = optional(string, null)
instance_types = list(string)
user_data = optional(object({
enabled = optional(bool, true)
template = optional(string, null)
content = optional(string, null)
pre_install = optional(string, "")
post_install = optional(string, "")
debug_logging_enabled = optional(bool, false)
}), {})
ssm_enabled = optional(bool, false)
create_service_linked_role_spot = optional(bool, false)
cloudwatch_agent = optional(object({
enabled = optional(bool, true)
config = optional(string, null)
}), {})
managed_security_group_enabled = optional(bool, true)
log_files = optional(list(object({
log_group_name = string
prefix_log_group = bool
file_path = string
log_stream_name = string
log_class = optional(string, "STANDARD")
})), null)
key_name = optional(string, null)
additional_security_group_ids = optional(list(string), [])
detailed_monitoring_enabled = optional(bool, false)
egress_rules = optional(list(object({
cidr_blocks = list(string)
ipv6_cidr_blocks = list(string)
prefix_list_ids = list(string)
from_port = number
protocol = string
security_groups = list(string)
self = bool
to_port = number
description = string
})), [{
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
prefix_list_ids = null
from_port = 0
protocol = "-1"
security_groups = null
self = null
to_port = 0
description = null
}])
tags = optional(map(string), {})
metadata_options = optional(object({
instance_metadata_tags = optional(string, "enabled")
http_endpoint = optional(string, "enabled")
http_tokens = optional(string, "required")
http_put_response_hop_limit = optional(number, 1)
}), {})
credit_specification = optional(string, null)
cpu_options = optional(object({
core_count = optional(number)
threads_per_core = optional(number)
amd_sev_snp = optional(string)
nested_virtualization = optional(string)
}), null)
placement = optional(object({
affinity = optional(string)
availability_zone = optional(string)
group_id = optional(string)
group_name = optional(string)
host_id = optional(string)
host_resource_group_arn = optional(string)
spread_domain = optional(string)
tenancy = optional(string)
partition_number = optional(number)
}), null)
license_specifications = optional(list(object({
license_configuration_arn = string
})), [])
associate_public_ipv4_address = optional(bool, false)
enable_on_demand_failover_for_errors = optional(list(string), [])
scale_errors = optional(list(string), [
"UnfulfillableCapacity",
"MaxSpotInstanceCountExceeded",
"TargetCapacityLimitExceededException",
"RequestLimitExceeded",
"ResourceLimitExceeded",
"MaxSpotInstanceCountExceeded",
"MaxSpotFleetRequestCountExceeded",
"InsufficientInstanceCapacity",
"InsufficientCapacityOnHost",
])
use_dedicated_host = optional(bool, false)
}), null)
})
| n/a | yes | +| [compute\_provider](#input\_compute\_provider) | Typed compute-provider configuration. Provider-owned settings remain inside the selected compute-provider block.

Exactly one compute-provider block must be non-null. The populated block selects the provider, and its presence must be known during planning. Values inside the selected block may remain unknown until apply.

- `ec2`: EC2 compute-provider configuration.
- `ec2.ami`: Optional AMI discovery or external AMI-parameter configuration. Null uses the operating-system and architecture defaults.
- `ec2.ami.filter`: EC2 AMI filters combined with the provider's default AMI-name filter.
- `ec2.ami.owners`: AWS account IDs or aliases allowed to own the selected AMI.
- `ec2.ami.id_ssm_parameter`: Optional externally managed SSM parameter containing the AMI ID. Null creates a provider-managed AMI-ID parameter. The wrapper's presence is the plan-time ownership discriminator, so keep the object literal even when its ARN comes from another resource.
- `ec2.ami.id_ssm_parameter.arn`: ARN of the externally managed SSM parameter. The ARN may be unknown until apply.
- `ec2.ami.kms_key`: Optional KMS key required to launch encrypted AMIs or snapshots. The wrapper's presence is the plan-time policy discriminator.
- `ec2.ami.kms_key.arn`: ARN of the KMS key. The ARN may be unknown until apply.
- `ec2.vpc_id`: VPC in which runner networking resources are created.
- `ec2.subnet_ids`: Subnets from which scale-up may launch runner instances.
- `ec2.overrides`: Optional resource-name overrides.
- `ec2.overrides.name_runner`: Name tag used for runner compute resources. An empty value uses the generated provider name.
- `ec2.overrides.name_sg`: Name tag used for the managed runner security group. An empty value uses the generated provider name.
- `ec2.instance_profile`: Optional externally managed instance profile used by the launch template.
- `ec2.instance_profile.name`: Name of the externally managed instance profile.
- `ec2.instance_profile_path`: IAM path for the provider-managed instance profile. Null uses a path derived from the stack prefix.
- `ec2.binaries_syncer`: Runner-distribution synchronization configuration.
- `ec2.binaries_syncer.enabled`: Enables use of a synchronized runner distribution from S3.
- `ec2.binaries_syncer.s3`: S3 object containing the synchronized runner distribution. Required when synchronization is enabled.
- `ec2.binaries_syncer.s3.arn`: ARN of the runner-distribution bucket, used by IAM policies.
- `ec2.binaries_syncer.s3.id`: Bucket name used to construct the runner-distribution S3 URI.
- `ec2.binaries_syncer.s3.key`: Object key of the runner distribution.
- `ec2.block_device_mappings`: EBS mappings added to the runner launch template.
- `ec2.block_device_mappings[].delete_on_termination`: Deletes the volume when its runner instance terminates.
- `ec2.block_device_mappings[].device_name`: Device name exposed to the runner instance.
- `ec2.block_device_mappings[].encrypted`: Enables EBS encryption.
- `ec2.block_device_mappings[].iops`: Provisioned IOPS for volume types that support it.
- `ec2.block_device_mappings[].kms_key_id`: KMS key ID or ARN used to encrypt the volume.
- `ec2.block_device_mappings[].snapshot_id`: Snapshot used to initialize the volume.
- `ec2.block_device_mappings[].throughput`: Provisioned throughput for volume types that support it.
- `ec2.block_device_mappings[].volume_initialization_rate`: Fixed initialization rate in MiB/s for supported snapshot-backed volumes.
- `ec2.block_device_mappings[].volume_size`: Volume size in GiB.
- `ec2.block_device_mappings[].volume_type`: EBS volume type.
- `ec2.ebs_optimized`: Requests EBS-optimized runner instances.
- `ec2.instance_target_capacity_type`: Primary capacity type, either `spot` or `on-demand`.
- `ec2.instance_allocation_strategy`: EC2 Fleet allocation strategy used to select instance capacity.
- `ec2.instance_type_priorities`: Optional numeric priorities keyed by instance type.
- `ec2.instance_max_spot_price`: Optional maximum hourly Spot price.
- `ec2.instance_types`: EC2 instance types available to the scale-up and pool functions.
- `ec2.user_data`: Runner bootstrap user-data configuration.
- `ec2.user_data.enabled`: Enables launch-template user data.
- `ec2.user_data.template`: Optional path to a custom user-data template.
- `ec2.user_data.content`: Optional complete user-data content. When set, it is used instead of rendering a template.
- `ec2.user_data.pre_install`: Script content inserted before runner installation in the default template.
- `ec2.user_data.post_install`: Script content inserted after runner installation in the default template.
- `ec2.user_data.debug_logging_enabled`: Enables verbose user-data tracing, which can expose secrets in logs.
- `ec2.ssm_enabled`: Attaches runner permissions and policies required for AWS Systems Manager access.
- `ec2.create_service_linked_role_spot`: Allows scale-up to create the EC2 Spot service-linked role.
- `ec2.cloudwatch_agent`: CloudWatch agent configuration for runner instances.
- `ec2.cloudwatch_agent.enabled`: Installs and configures the CloudWatch agent through the default bootstrap flow.
- `ec2.cloudwatch_agent.config`: Optional complete CloudWatch agent configuration. Null renders the provider default from `log_files`.
- `ec2.managed_security_group_enabled`: Creates and attaches the provider-managed runner security group.
- `ec2.log_files`: Optional log files collected by the CloudWatch agent. Null uses the provider defaults.
- `ec2.log_files[].log_group_name`: CloudWatch log-group name, before optional prefixing.
- `ec2.log_files[].prefix_log_group`: Prefixes the log-group name with the runner stack path when true.
- `ec2.log_files[].file_path`: File or glob read by the CloudWatch agent.
- `ec2.log_files[].log_stream_name`: CloudWatch log-stream name template.
- `ec2.log_files[].log_class`: CloudWatch log-group class for the collected file.
- `ec2.key_name`: Optional EC2 key-pair name added to the launch template.
- `ec2.additional_security_group_ids`: Existing security groups attached in addition to the managed security group.
- `ec2.detailed_monitoring_enabled`: Enables detailed EC2 monitoring for runner instances.
- `ec2.egress_rules`: Egress rules created on the managed runner security group.
- `ec2.egress_rules[].cidr_blocks`: IPv4 CIDR destinations.
- `ec2.egress_rules[].ipv6_cidr_blocks`: IPv6 CIDR destinations.
- `ec2.egress_rules[].prefix_list_ids`: AWS prefix-list destinations.
- `ec2.egress_rules[].from_port`: First destination port in the permitted range.
- `ec2.egress_rules[].protocol`: IP protocol name or number. Use `-1` for all protocols.
- `ec2.egress_rules[].security_groups`: Destination security-group IDs.
- `ec2.egress_rules[].self`: Allows traffic to the managed security group itself when true.
- `ec2.egress_rules[].to_port`: Last destination port in the permitted range.
- `ec2.egress_rules[].description`: Optional rule description.
- `ec2.tags`: Additional tags for runner instances, EBS volumes, network interfaces, and eligible Spot instance requests created from the launch template. They override module-level tags and the generated runner `Name`; the provider-managed `ghr:environment`, `ghr:ssm_config_path`, and `ghr:runner_name_prefix` bootstrap tags take final precedence. These tags do not apply to static provider resources such as the launch template, security group, IAM resources, SSM parameters, or log groups.
- `ec2.metadata_options`: Instance Metadata Service configuration in the launch template.
- `ec2.metadata_options.instance_metadata_tags`: Exposes instance tags through Instance Metadata Service when `enabled`.
- `ec2.metadata_options.http_endpoint`: Enables or disables the Instance Metadata Service endpoint.
- `ec2.metadata_options.http_tokens`: Controls whether IMDSv2 session tokens are optional or required.
- `ec2.metadata_options.http_put_response_hop_limit`: Network hop limit for Instance Metadata Service token responses.
- `ec2.credit_specification`: CPU credit mode for burstable instance types, either `standard` or `unlimited`.
- `ec2.cpu_options`: CPU topology and processor-feature configuration.
- `ec2.cpu_options.core_count`: Number of CPU cores exposed to the runner instance.
- `ec2.cpu_options.threads_per_core`: Number of hardware threads exposed per CPU core.
- `ec2.cpu_options.amd_sev_snp`: Enables or disables AMD SEV-SNP on supported instance types.
- `ec2.cpu_options.nested_virtualization`: Enables or disables nested virtualization on supported instance types.
- `ec2.placement`: EC2 placement configuration for runner instances.
- `ec2.placement.affinity`: Host affinity setting.
- `ec2.placement.availability_zone`: Availability Zone in which the instance is placed.
- `ec2.placement.group_id`: Placement-group ID.
- `ec2.placement.group_name`: Placement-group name.
- `ec2.placement.host_id`: Dedicated Host ID.
- `ec2.placement.host_resource_group_arn`: ARN of the host resource group used for placement.
- `ec2.placement.spread_domain`: Spread-domain placement value.
- `ec2.placement.tenancy`: Instance tenancy, such as `default`, `dedicated`, or `host`.
- `ec2.placement.partition_number`: Placement-group partition number.
- `ec2.license_specifications`: License Manager configurations added to the launch template.
- `ec2.license_specifications[].license_configuration_arn`: ARN of a License Manager license configuration.
- `ec2.associate_public_ipv4_address`: Associates a public IPv4 address with runner network interfaces.
- `ec2.enable_on_demand_failover_for_errors`: EC2 error codes that trigger an on-demand fallback after a Spot launch failure.
- `ec2.scale_errors`: EC2 error codes treated as retryable scale-up failures.
- `ec2.use_dedicated_host`: Enables the dedicated-host launch path, required for macOS runners.
- `microvm`: Lambda MicroVM compute-provider configuration.
- `microvm.image_identifier`: ARN or ID of the MicroVM image used to run GitHub runners.
- `microvm.image_version`: Optional MicroVM image version.
- `microvm.execution_role.arn`: Optional externally managed execution role assumed by MicroVMs. Null uses the common runner role.
- `microvm.egress_network_connectors`: Egress network connectors passed to RunMicrovm.
- `microvm.idle_policy`: Optional auto-suspend and auto-resume configuration passed to RunMicrovm.
- `microvm.logging`: Optional RunMicrovm logging union. Exactly one of `cloud_watch` or `disabled` must be selected when set.
- `microvm.run_hook_payload`: Optional payload delivered to the MicroVM `/run` hook. Maximum 16,384 characters.
- `microvm.maximum_duration_in_seconds`: Optional maximum MicroVM lifetime. Valid range is 1 through 28,800 seconds.
- `microvm.environment_variables`: Additional provider-specific Lambda environment variables merged into scale-up, scale-down, and pool.
- `microvm.tags`: Tags encoded into the MicroVM runner configuration.
- `microvm.iam`: Optional MicroVM control-plane IAM overrides and managed policy attachments. |
object({
ec2 = optional(object({
ami = optional(object({
filter = optional(map(list(string)), { state = ["available"] })
owners = optional(list(string), ["amazon"])
id_ssm_parameter = optional(object({
arn = string
}), null)
kms_key = optional(object({
arn = string
}), null)
}), null)
vpc_id = string
subnet_ids = list(string)
overrides = optional(object({
name_runner = optional(string, "")
name_sg = optional(string, "")
}), {})
instance_profile = optional(object({
name = string
}), null)
instance_profile_path = optional(string, null)
binaries_syncer = optional(object({
enabled = optional(bool, true)
s3 = optional(object({
arn = string
id = string
key = string
}), null)
}), {})
block_device_mappings = optional(list(object({
delete_on_termination = optional(bool, true)
device_name = optional(string, "/dev/xvda")
encrypted = optional(bool, true)
iops = optional(number)
kms_key_id = optional(string)
snapshot_id = optional(string)
throughput = optional(number)
volume_initialization_rate = optional(number)
volume_size = number
volume_type = optional(string, "gp3")
})), [{ volume_size = 30 }])
ebs_optimized = optional(bool, false)
instance_target_capacity_type = optional(string, "spot")
instance_allocation_strategy = optional(string, "lowest-price")
instance_type_priorities = optional(map(number), null)
instance_max_spot_price = optional(string, null)
instance_types = list(string)
user_data = optional(object({
enabled = optional(bool, true)
template = optional(string, null)
content = optional(string, null)
pre_install = optional(string, "")
post_install = optional(string, "")
debug_logging_enabled = optional(bool, false)
}), {})
ssm_enabled = optional(bool, false)
create_service_linked_role_spot = optional(bool, false)
cloudwatch_agent = optional(object({
enabled = optional(bool, true)
config = optional(string, null)
}), {})
managed_security_group_enabled = optional(bool, true)
log_files = optional(list(object({
log_group_name = string
prefix_log_group = bool
file_path = string
log_stream_name = string
log_class = optional(string, "STANDARD")
})), null)
key_name = optional(string, null)
additional_security_group_ids = optional(list(string), [])
detailed_monitoring_enabled = optional(bool, false)
egress_rules = optional(list(object({
cidr_blocks = list(string)
ipv6_cidr_blocks = list(string)
prefix_list_ids = list(string)
from_port = number
protocol = string
security_groups = list(string)
self = bool
to_port = number
description = string
})), [{
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
prefix_list_ids = null
from_port = 0
protocol = "-1"
security_groups = null
self = null
to_port = 0
description = null
}])
tags = optional(map(string), {})
metadata_options = optional(object({
instance_metadata_tags = optional(string, "enabled")
http_endpoint = optional(string, "enabled")
http_tokens = optional(string, "required")
http_put_response_hop_limit = optional(number, 1)
}), {})
credit_specification = optional(string, null)
cpu_options = optional(object({
core_count = optional(number)
threads_per_core = optional(number)
amd_sev_snp = optional(string)
nested_virtualization = optional(string)
}), null)
placement = optional(object({
affinity = optional(string)
availability_zone = optional(string)
group_id = optional(string)
group_name = optional(string)
host_id = optional(string)
host_resource_group_arn = optional(string)
spread_domain = optional(string)
tenancy = optional(string)
partition_number = optional(number)
}), null)
license_specifications = optional(list(object({
license_configuration_arn = string
})), [])
associate_public_ipv4_address = optional(bool, false)
enable_on_demand_failover_for_errors = optional(list(string), [])
scale_errors = optional(list(string), [
"UnfulfillableCapacity",
"MaxSpotInstanceCountExceeded",
"TargetCapacityLimitExceededException",
"RequestLimitExceeded",
"ResourceLimitExceeded",
"MaxSpotInstanceCountExceeded",
"MaxSpotFleetRequestCountExceeded",
"InsufficientInstanceCapacity",
"InsufficientCapacityOnHost",
])
use_dedicated_host = optional(bool, false)
}), null)

microvm = optional(object({
image_identifier = string
image_version = optional(string, null)
execution_role = optional(object({
arn = string
}), null)
egress_network_connectors = optional(list(string), [])
idle_policy = optional(object({
max_idle_duration_seconds = number
suspended_duration_seconds = number
auto_resume_enabled = bool
}), null)
logging = optional(object({
cloud_watch = optional(object({
log_group = optional(string, null)
log_stream = optional(string, null)
}), null)
disabled = optional(bool, false)
}), null)
run_hook_payload = optional(string, null)
maximum_duration_in_seconds = optional(number, null)
environment_variables = optional(map(string), {})
tags = optional(map(string), {})
iam = optional(object({
resource_arns = optional(list(string), ["*"])
actions = optional(object({
scale_up = optional(list(string), null)
scale_down = optional(list(string), null)
}), {})
additional_policy_json = optional(object({
scale_up = optional(string, null)
}), {})
managed_policy_arns = optional(object({
scale_up = optional(string, null)
pool = optional(string, null)
}), {})
}), {})
}), null)
})
| n/a | yes | | [github](#input\_github) | GitHub API and runner-registration configuration.

- `app_parameters.key_base64`: Parameter Store reference for the GitHub App private key.
- `app_parameters.key_base64.name`: Name of the private-key parameter supplied to the control-plane functions.
- `app_parameters.key_base64.arn`: ARN of the private-key parameter used by IAM policies.
- `app_parameters.id`: Parameter Store reference for the GitHub App ID.
- `app_parameters.id.name`: Name of the App-ID parameter supplied to the control-plane functions.
- `app_parameters.id.arn`: ARN of the App-ID parameter used by IAM policies.
- `organization_runners`: Registers runners at organization scope when true; otherwise repository-scoped registration is used.
- `enterprise_server.url`: Optional GitHub Enterprise Server base URL. Null selects GitHub.com.
- `enterprise_server.ssl_verify`: Enables TLS certificate verification for GitHub Enterprise Server requests.
- `user_agent`: Optional User-Agent value added to GitHub API requests. |
object({
app_parameters = object({
key_base64 = map(string)
id = map(string)
})
organization_runners = bool
enterprise_server = optional(object({
url = optional(string, null)
ssl_verify = optional(bool, true)
}), {})
user_agent = optional(string, null)
})
| n/a | yes | | [job\_retry](#input\_job\_retry) | Job-retry queue and Lambda configuration.

- `enabled`: Creates the retry queue, Lambda function, event-source mapping, and related IAM resources.
- `delay_in_seconds`: Initial delay before a queued-job retry check. AWS SQS limits this value to 900 seconds.
- `delay_backoff`: Multiplier applied to the delay after each unsuccessful check.
- `max_attempts`: Maximum retry-check attempts before the message is no longer republished.
- `tags`: Tags for job-retry resources, including the Lambda function, log group, IAM role, retry queue, and event-source mapping. These override module-level tags and the shared `lambda.tags`, `queue.tags`, and `observability.logs.tags` maps when keys conflict.
- `lambda.memory_size`: Memory allocated to the job-retry Lambda in MB.
- `lambda.reserved_concurrent_executions`: Reserved concurrency for the job-retry Lambda. Use `-1` for unreserved concurrency.
- `lambda.timeout`: Job-retry Lambda timeout in seconds and visibility timeout for its retry queue. |
object({
enabled = optional(bool, false)
delay_in_seconds = optional(number, 300)
delay_backoff = optional(number, 2)
max_attempts = optional(number, 1)
tags = optional(map(string), {})
lambda = optional(object({
memory_size = optional(number, 256)
reserved_concurrent_executions = optional(number, 1)
timeout = optional(number, 30)
}), {})
})
| `{}` | no | | [lambda](#input\_lambda) | Configuration shared by the control-plane Lambda functions.

- `zip`: Local control-plane archive. When null, the module's packaged runner archive is used.
- `s3.bucket`: Optional S3 bucket containing the Lambda archive. Setting this selects S3 instead of a local archive.
- `s3.key`: Object key of the Lambda archive in `s3.bucket`.
- `s3.object_version`: Optional version of the Lambda archive object.
- `runtime`: Runtime used by all control-plane Lambda functions.
- `architecture`: Instruction-set architecture used by all control-plane Lambda functions. Supported values are `arm64` and `x86_64`.
- `subnet_ids`: Subnets used for Lambda VPC configuration.
- `security_group_ids`: Security groups used for Lambda VPC configuration.
- `tags`: Shared tags applied to Lambda function resources only. These override module-level `tags`; component `tags` override this map when keys conflict.
- `role.path`: IAM path for module-managed Lambda execution roles. Defaults to a path derived from `prefix`.
- `role.permissions_boundary`: Permissions-boundary ARN applied to module-managed Lambda execution roles. |
object({
zip = optional(string, null)
s3 = optional(object({
bucket = optional(string, null)
key = optional(string, null)
object_version = optional(string, null)
}), {})
runtime = optional(string, "nodejs24.x")
architecture = optional(string, "arm64")
subnet_ids = optional(list(string), [])
security_group_ids = optional(list(string), [])
tags = optional(map(string), {})
role = optional(object({
path = optional(string, null)
permissions_boundary = optional(string, null)
}), {})
})
| `{}` | no | diff --git a/modules/webhook/README.md b/modules/webhook/README.md index 458ff5a7aa..f104d69d15 100644 --- a/modules/webhook/README.md +++ b/modules/webhook/README.md @@ -89,7 +89,7 @@ yarn run dist | [repository\_white\_list](#input\_repository\_white\_list) | List of github repository full names (owner/repo\_name) that will be allowed to use the github app. Leave empty for no filtering. | `list(string)` | `[]` | no | | [role\_path](#input\_role\_path) | The path that will be added to the role; if not set, the environment name will be used. | `string` | `null` | no | | [role\_permissions\_boundary](#input\_role\_permissions\_boundary) | Permissions boundary that will be added to the created role for the lambda. | `string` | `null` | no | -| [runner\_matcher\_config](#input\_runner\_matcher\_config) | SQS queue to publish accepted build events based on the runner type. `computeProvider` defaults to `ec2`; EC2 is the only provider currently implemented. When exact match is disabled the webhook accepts the event if one of the workflow job labels is part of the matcher. The priority defines the order the matchers are applied. Optional `matcherConfig.enableDynamicLabels` and `matcherConfig.awsDynamicLabelsPolicy` are evaluated by the dispatcher to gate provider dynamic labels per runner. The policy supports `blocked_keys = []` and `restricted_keys = { = { allowed = [globs], denied = [globs], max = number|string } }`; keys use the provider dynamic label suffix form, for example `instance-type` for `ghr-ec2-instance-type`. |
map(object({
arn = string
id = string
computeProvider = optional(string, "ec2")
matcherConfig = object({
labelMatchers = list(list(string))
exactMatch = bool
bidirectionalLabelMatch = optional(bool, false)
priority = optional(number, 999)
enableDynamicLabels = optional(bool, false)
awsDynamicLabelsPolicy = optional(object({
blocked_keys = optional(list(string), [])
restricted_keys = optional(map(object({
allowed = optional(list(string), [])
denied = optional(list(string), [])
max = optional(string, null)
})), {})
}), null)
})
}))
| n/a | yes | +| [runner\_matcher\_config](#input\_runner\_matcher\_config) | SQS queue to publish accepted build events based on the runner type. `computeProvider` defaults to `ec2`; supported values are `ec2` and `microvm`. When exact match is disabled the webhook accepts the event if one of the workflow job labels is part of the matcher. The priority defines the order the matchers are applied. Optional `matcherConfig.enableDynamicLabels` and `matcherConfig.awsDynamicLabelsPolicy` are evaluated by the dispatcher to gate provider dynamic labels per runner. The policy supports `blocked_keys = []` and `restricted_keys = { = { allowed = [globs], denied = [globs], max = number|string } }`; keys use the provider dynamic label suffix form, for example `instance-type` for `ghr-ec2-instance-type`. |
map(object({
arn = string
id = string
computeProvider = optional(string, "ec2")
matcherConfig = object({
labelMatchers = list(list(string))
exactMatch = bool
bidirectionalLabelMatch = optional(bool, false)
priority = optional(number, 999)
enableDynamicLabels = optional(bool, false)
awsDynamicLabelsPolicy = optional(object({
blocked_keys = optional(list(string), [])
restricted_keys = optional(map(object({
allowed = optional(list(string), [])
denied = optional(list(string), [])
max = optional(string, null)
})), {})
}), null)
})
}))
| n/a | yes | | [ssm\_paths](#input\_ssm\_paths) | The root path used in SSM to store configuration and secrets. |
object({
root = string
webhook = string
})
| n/a | yes | | [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 | | [tracing\_config](#input\_tracing\_config) | Configuration for lambda tracing. |
object({
mode = optional(string, null)
capture_http_requests = optional(bool, false)
capture_error = optional(bool, false)
})
| `{}` | no |