/scale-set-controller`.
+ EOT
+ type = object({
+ path_prefix = optional(string, null)
+ tier = optional(string, "Standard")
+ tags = optional(map(string), {})
+ })
+ default = {}
+ nullable = false
+}
+
+variable "ecs" {
+ description = <<-EOT
+ ECS substrate configuration. A managed cluster is created by default. For an external cluster, set `cluster.mode = "external"` and pass its ARN; the mode must be plan-known while the ARN may be computed.
+ EOT
+ type = object({
+ cluster = optional(object({
+ mode = optional(string, "managed")
+ arn = optional(string, null)
+ name = optional(string, null)
+ container_insights = optional(bool, true)
+ }), {})
+ task = optional(object({
+ cpu = optional(number, 512)
+ memory = optional(number, 1024)
+ cpu_architecture = optional(string, "X86_64")
+ ephemeral_storage = optional(object({
+ size_in_gib = number
+ }), null)
+ }), {})
+ service = optional(object({
+ platform_version = optional(string, "LATEST")
+ }), {})
+ iam = optional(object({
+ path = optional(string, "/")
+ permissions_boundary = optional(string, null)
+ }), {})
+ })
+ default = {}
+ nullable = false
+}
+
+variable "network" {
+ description = <<-EOT
+ Private Fargate networking. Tasks never receive public IP addresses and the managed security groups have no ingress. HTTPS egress defaults to IPv4 Internet access because GitHub endpoints cannot be represented as security-group destinations; route it through controlled NAT, firewall, or proxy infrastructure when required.
+ EOT
+ type = object({
+ vpc_id = string
+ subnet_ids = set(string)
+ https_egress = optional(object({
+ ipv4_cidrs = optional(set(string), ["0.0.0.0/0"])
+ ipv6_cidrs = optional(set(string), [])
+ }), {})
+ })
+ nullable = false
+}
+
+variable "logging" {
+ description = "CloudWatch Logs configuration. CloudWatch encrypts logs at rest with an AWS-owned key by default; set `kms_key_arn` to use a customer-managed key."
+ type = object({
+ retention_in_days = optional(number, 30)
+ kms_key_arn = optional(string, null)
+ log_group_class = optional(string, "STANDARD")
+ tags = optional(map(string), {})
+ })
+ default = {}
+ nullable = false
+}
+
+variable "tags" {
+ description = "Tags applied to scale-set orchestration resources."
+ type = map(string)
+ default = {}
+ nullable = false
+}
diff --git a/modules/orchestration-providers/scale-set/versions.tf b/modules/orchestration-providers/scale-set/versions.tf
new file mode 100644
index 0000000000..0bedc91fd5
--- /dev/null
+++ b/modules/orchestration-providers/scale-set/versions.tf
@@ -0,0 +1,10 @@
+terraform {
+ required_version = ">= 1.5.6"
+
+ required_providers {
+ aws = {
+ source = "hashicorp/aws"
+ version = ">= 6.33"
+ }
+ }
+}
diff --git a/modules/orchestration-providers/webhook/README.md b/modules/orchestration-providers/webhook/README.md
index 798f22ec8e..53d0115ebb 100644
--- a/modules/orchestration-providers/webhook/README.md
+++ b/modules/orchestration-providers/webhook/README.md
@@ -11,7 +11,7 @@ The scale-down lifecycle is documented in the [scale-down state diagram](./scale
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.4.0 |
| [aws](#requirement\_aws) | >= 6.33 |
## Providers
@@ -39,8 +39,8 @@ The scale-down lifecycle is documented in the [scale-down state diagram](./scale
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| [aws\_partition](#input\_aws\_partition) | AWS partition used to construct ARNs. | `string` | `"aws"` | no |
-| [config](#input\_config) | Provider-owned webhook values supplied from `orchestration_provider.webhook`. The parent resolves inherited input values before calling this module; this provider still resolves the documented JIT, artifact, and tag-precedence fallbacks.
- `runner`: Runner lifecycle, boot timeout, and capacity settings owned by webhook orchestration.
- `runner.boot_time_in_minutes`: Expected runner boot duration used by scale-down and pool controls.
- `runner.ephemeral`: Registers runners in ephemeral mode.
- `runner.jit_config_enabled`: Explicitly enables or disables just-in-time configuration. Null follows `runner.ephemeral`.
- `runner.maximum_count`: Maximum number of runners managed for this runner configuration.
- `github.organization_runners`: Registers runners at organization scope when true; otherwise registration is repository-scoped.
- `queue.build.arn`: ARN of the runner configuration's build queue.
- `queue.build.url`: URL of the runner configuration's build queue.
- `queue.kms_key_id`: Optional KMS key ARN encrypting the build queue. This is independent from the Parameter Store KMS key.
- `queue.tags`: Tags inherited by queue-related provider resources before component-specific overrides.
- `lambda.artifact`: Runner-control artifact shared by scale, pool, and job-retry components. At most one of `zip` or `s3` may be selected; no selection uses the packaged runner archive.
- `lambda.artifact.zip`: Optional local path to the runner-control Lambda archive.
- `lambda.artifact.s3`: Optional S3 object selector in the common `lambda.artifact.s3.bucket`. Wrapper presence must be known during planning and selecting it requires a non-null common bucket.
- `lambda.artifact.s3.key`: Object key of the runner-control Lambda archive.
- `lambda.artifact.s3.object_version`: Optional object version of the runner-control Lambda archive.
- `lambda.scale.up.memory_size`: Memory allocated to the scale-up Lambda in MB.
- `lambda.scale.up.timeout`: Scale-up Lambda timeout in seconds.
- `lambda.scale.up.reserved_concurrent_executions`: Reserved concurrency for scale-up. Use `-1` for unreserved concurrency.
- `lambda.scale.up.job_queued_check_enabled`: Enables queued-job verification before scaling. Null follows the resolved runner mode.
- `lambda.scale.up.event_source_mapping.batch_size`: Maximum build-queue records delivered per scale-up invocation.
- `lambda.scale.up.event_source_mapping.maximum_batching_window_in_seconds`: Maximum batching window for build-queue records.
- `lambda.scale.up.tags`: Tags applied within scale-up resource scopes after common provider tags.
- `lambda.scale.down.memory_size`: Memory allocated to the scale-down Lambda in MB.
- `lambda.scale.down.timeout`: Scale-down Lambda timeout in seconds.
- `lambda.scale.down.schedule_expression`: EventBridge schedule expression that invokes scale-down.
- `lambda.scale.down.minimum_running_time_in_minutes`: Optional minimum runner age before scale-down may terminate it. Null selects the operating-system default.
- `lambda.scale.down.idle_confirmation_seconds`: Number of seconds a runner must consistently report not-busy before scale-down terminates it. A value of `0` preserves the single-reading behavior.
- `lambda.scale.down.idle_config`: Time-based desired idle-runner configurations.
- `lambda.scale.down.idle_config[].cron`: Cron expression identifying when the idle configuration applies.
- `lambda.scale.down.idle_config[].timeZone`: IANA time zone used to evaluate the cron expression.
- `lambda.scale.down.idle_config[].idleCount`: Number of idle runners retained during the matching period.
- `lambda.scale.down.idle_config[].evictionStrategy`: Selection strategy used when excess idle runners are removed.
- `lambda.scale.down.tags`: Tags applied within scale-down resource scopes after common provider tags.
- `lambda.pool.memory_size`: Memory allocated to the pool Lambda in MB.
- `lambda.pool.timeout`: Pool Lambda timeout in seconds.
- `lambda.pool.reserved_concurrent_executions`: Reserved concurrency for the pool Lambda. Use `-1` for unreserved concurrency.
- `lambda.pool.config`: Scheduled target pool sizes. An empty list disables the pool component.
- `lambda.pool.config[].schedule_expression`: Scheduler expression that activates the target size.
- `lambda.pool.config[].schedule_expression_timezone`: Optional IANA time zone used to evaluate the schedule.
- `lambda.pool.config[].size`: Desired number of runners for the schedule.
- `lambda.pool.include_busy_runners`: Includes busy runners when reconciling scheduled pool capacity.
- `lambda.pool.runner_owner`: Optional GitHub organization or repository owner used for pooled runners.
- `lambda.pool.tags`: Tags applied within pool resource scopes after common provider tags.
- `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 applied within job-retry resource scopes after common provider tags.
- `job_retry.lambda.memory_size`: Memory allocated to the job-retry Lambda in MB.
- `job_retry.lambda.reserved_concurrent_executions`: Reserved concurrency for job retry. Use `-1` for unreserved concurrency.
- `job_retry.lambda.timeout`: Job-retry Lambda timeout in seconds and visibility timeout for its retry queue. | object({
runner = object({
boot_time_in_minutes = number
ephemeral = bool
jit_config_enabled = optional(bool, null)
maximum_count = number
})
github = object({
organization_runners = bool
})
queue = object({
build = object({
arn = string
url = string
})
kms_key_id = optional(string, null)
tags = optional(map(string), {})
})
lambda = object({
artifact = object({
zip = optional(string, null)
s3 = optional(object({
key = string
object_version = optional(string, null)
}), null)
})
scale = object({
up = object({
memory_size = number
timeout = number
reserved_concurrent_executions = number
job_queued_check_enabled = optional(bool, null)
event_source_mapping = object({
batch_size = number
maximum_batching_window_in_seconds = number
})
tags = optional(map(string), {})
})
down = object({
memory_size = number
timeout = number
schedule_expression = string
minimum_running_time_in_minutes = optional(number, null)
idle_confirmation_seconds = optional(number, 0)
idle_config = list(object({
cron = string
timeZone = string
idleCount = number
evictionStrategy = string
}))
tags = optional(map(string), {})
})
})
pool = object({
memory_size = number
timeout = number
reserved_concurrent_executions = number
config = list(object({
schedule_expression = string
schedule_expression_timezone = optional(string)
size = number
}))
include_busy_runners = bool
runner_owner = optional(string, null)
tags = optional(map(string), {})
})
})
job_retry = object({
enabled = bool
delay_in_seconds = number
delay_backoff = number
max_attempts = number
tags = optional(map(string), {})
lambda = object({
memory_size = number
reserved_concurrent_executions = number
timeout = number
})
})
}) | n/a | yes |
-| [github](#input\_github) | Common GitHub API client and GitHub App Parameter Store references. | object({
app_parameters = object({
key_base64 = map(string)
id = map(string)
additional_apps_manifest = optional(object({
name = string
arn = string
}), null)
additional_app_parameter_arns = optional(list(string), [])
})
enterprise_server = object({
url = optional(string, null)
ssl_verify = bool
})
user_agent = optional(string, null)
}) | n/a | yes |
+| [config](#input\_config) | Provider-owned webhook values supplied from `orchestration_provider.webhook`. The parent resolves inherited input values before calling this module; this provider still resolves the documented JIT, artifact, and tag-precedence fallbacks.
- `runner`: Runner lifecycle, boot timeout, and capacity settings owned by webhook orchestration.
- `runner.boot_time_in_minutes`: Expected runner boot duration used by scale-down and pool controls.
- `runner.ephemeral`: Registers runners in ephemeral mode.
- `runner.jit_config_enabled`: Explicitly enables or disables just-in-time configuration. Null follows `runner.ephemeral`.
- `runner.maximum_count`: Maximum number of runners managed for this runner configuration.
- `github.organization_runners`: Registers runners at organization scope when true; otherwise registration is repository-scoped.
- `queue.build.arn`: ARN of the runner configuration's build queue.
- `queue.build.url`: URL of the runner configuration's build queue.
- `queue.kms_key_id`: Optional KMS key ARN encrypting the build queue. This is independent from the Parameter Store KMS key.
- `queue.tags`: Tags inherited by queue-related provider resources before component-specific overrides.
- `lambda.artifact`: Runner-control artifact shared by scale, pool, and job-retry components. At most one of `zip` or `s3` may be selected; no selection uses the packaged runner archive.
- `lambda.artifact.zip`: Optional local path to the runner-control Lambda archive.
- `lambda.artifact.s3`: Optional S3 object selector in the common `lambda.artifact.s3.bucket`. Wrapper presence must be known during planning and selecting it requires a non-null common bucket.
- `lambda.artifact.s3.key`: Object key of the runner-control Lambda archive.
- `lambda.artifact.s3.object_version`: Optional object version of the runner-control Lambda archive.
- `lambda.scale.up.memory_size`: Memory allocated to the scale-up Lambda in MB.
- `lambda.scale.up.timeout`: Scale-up Lambda timeout in seconds.
- `lambda.scale.up.reserved_concurrent_executions`: Reserved concurrency for scale-up. Use `-1` for unreserved concurrency.
- `lambda.scale.up.job_queued_check_enabled`: Enables queued-job verification before scaling. Null follows the resolved runner mode.
- `lambda.scale.up.event_source_mapping.batch_size`: Maximum build-queue records delivered per scale-up invocation.
- `lambda.scale.up.event_source_mapping.maximum_batching_window_in_seconds`: Maximum batching window for build-queue records.
- `lambda.scale.up.tags`: Tags applied within scale-up resource scopes after common provider tags.
- `lambda.scale.down.memory_size`: Memory allocated to the scale-down Lambda in MB.
- `lambda.scale.down.timeout`: Scale-down Lambda timeout in seconds.
- `lambda.scale.down.schedule_expression`: EventBridge schedule expression that invokes scale-down.
- `lambda.scale.down.minimum_running_time_in_minutes`: Optional minimum runner age before scale-down may terminate it. Null selects the operating-system default.
- `lambda.scale.down.idle_config`: Time-based desired idle-runner configurations.
- `lambda.scale.down.idle_config[].cron`: Cron expression identifying when the idle configuration applies.
- `lambda.scale.down.idle_config[].timeZone`: IANA time zone used to evaluate the cron expression.
- `lambda.scale.down.idle_config[].idleCount`: Number of idle runners retained during the matching period.
- `lambda.scale.down.idle_config[].evictionStrategy`: Selection strategy used when excess idle runners are removed.
- `lambda.scale.down.tags`: Tags applied within scale-down resource scopes after common provider tags.
- `lambda.pool.memory_size`: Memory allocated to the pool Lambda in MB.
- `lambda.pool.timeout`: Pool Lambda timeout in seconds.
- `lambda.pool.reserved_concurrent_executions`: Reserved concurrency for the pool Lambda. Use `-1` for unreserved concurrency.
- `lambda.pool.config`: Scheduled target pool sizes. An empty list disables the pool component.
- `lambda.pool.config[].schedule_expression`: Scheduler expression that activates the target size.
- `lambda.pool.config[].schedule_expression_timezone`: Optional IANA time zone used to evaluate the schedule.
- `lambda.pool.config[].size`: Desired number of runners for the schedule.
- `lambda.pool.include_busy_runners`: Includes busy runners when reconciling scheduled pool capacity.
- `lambda.pool.runner_owner`: Optional GitHub organization or repository owner used for pooled runners.
- `lambda.pool.tags`: Tags applied within pool resource scopes after common provider tags.
- `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 applied within job-retry resource scopes after common provider tags.
- `job_retry.lambda.memory_size`: Memory allocated to the job-retry Lambda in MB.
- `job_retry.lambda.reserved_concurrent_executions`: Reserved concurrency for job retry. Use `-1` for unreserved concurrency.
- `job_retry.lambda.timeout`: Job-retry Lambda timeout in seconds and visibility timeout for its retry queue. | object({
runner = object({
boot_time_in_minutes = number
ephemeral = bool
jit_config_enabled = optional(bool, null)
maximum_count = number
})
github = object({
organization_runners = bool
})
queue = object({
build = object({
arn = string
url = string
})
kms_key_id = optional(string, null)
tags = optional(map(string), {})
})
lambda = object({
artifact = object({
zip = optional(string, null)
s3 = optional(object({
key = string
object_version = optional(string, null)
}), null)
})
scale = object({
up = object({
memory_size = number
timeout = number
reserved_concurrent_executions = number
job_queued_check_enabled = optional(bool, null)
event_source_mapping = object({
batch_size = number
maximum_batching_window_in_seconds = number
})
tags = optional(map(string), {})
})
down = object({
memory_size = number
timeout = number
schedule_expression = string
minimum_running_time_in_minutes = optional(number, null)
idle_config = list(object({
cron = string
timeZone = string
idleCount = number
evictionStrategy = string
}))
tags = optional(map(string), {})
})
})
pool = object({
memory_size = number
timeout = number
reserved_concurrent_executions = number
config = list(object({
schedule_expression = string
schedule_expression_timezone = optional(string)
size = number
}))
include_busy_runners = bool
runner_owner = optional(string, null)
tags = optional(map(string), {})
})
})
job_retry = object({
enabled = bool
delay_in_seconds = number
delay_backoff = number
max_attempts = number
tags = optional(map(string), {})
lambda = object({
memory_size = number
reserved_concurrent_executions = number
timeout = number
})
})
}) | n/a | yes |
+| [github](#input\_github) | Common GitHub API client and GitHub App Parameter Store references. | object({
app_parameters = object({
key_base64 = list(map(string))
id = list(map(string))
installation_id = list(object({ name = string, arn = string }))
})
enterprise_server = object({
url = optional(string, null)
ssl_verify = bool
})
user_agent = optional(string, null)
}) | n/a | yes |
| [lambda](#input\_lambda) | Common Lambda substrate. Only the shared artifact bucket crosses this boundary; the webhook provider owns its archive key, version, and local zip selection. | object({
artifact = object({
s3 = object({
bucket = optional(string, null)
})
})
runtime = string
architecture = string
subnet_ids = list(string)
security_group_ids = list(string)
tags = optional(map(string), {})
role = object({
path = string
permissions_boundary = optional(string, null)
principals = optional(list(object({
type = string
identifiers = list(string)
})), [])
})
}) | n/a | yes |
| [observability](#input\_observability) | Common logging, tracing, and metrics configuration consumed by webhook controls. | object({
logs = object({
level = string
retention_in_days = number
kms_key_id = optional(string, null)
class = string
tags = optional(map(string), {})
})
tracing = object({
mode = optional(string, null)
capture_http_requests = bool
capture_error = bool
})
metrics = object({
enabled = bool
namespace = string
metric = object({
github_app_rate_limit = object({
enabled = bool
})
job_retry = object({
enabled = bool
})
})
})
}) | n/a | yes |
| [prefix](#input\_prefix) | Prefix used to identify resources created for this webhook orchestration provider. | `string` | n/a | yes |
diff --git a/modules/orchestration-providers/webhook/job-retry/README.md b/modules/orchestration-providers/webhook/job-retry/README.md
index 999535a4e2..9c6e4e0f52 100644
--- a/modules/orchestration-providers/webhook/job-retry/README.md
+++ b/modules/orchestration-providers/webhook/job-retry/README.md
@@ -12,7 +12,7 @@ The module is an inner module used by the webhook orchestration provider when th
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.4.0 |
| [aws](#requirement\_aws) | >= 6.21 |
## Providers
@@ -52,7 +52,7 @@ No modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [config](#input\_config) | Provider-neutral job-retry configuration assembled by runner-config.
- `prefix`: Prefix used to name job-retry resources.
- `aws_partition`: AWS partition used to construct the Lambda VPC managed-policy ARN.
- `lambda.artifact.zip`: Resolved local control-plane archive.
- `lambda.artifact.s3.bucket`: Optional S3 bucket containing the Lambda archive.
- `lambda.artifact.s3.key`: Object key of the Lambda archive.
- `lambda.artifact.s3.object_version`: Optional object version of the Lambda archive.
- `lambda.runtime`: Runtime used by the job-retry Lambda.
- `lambda.architecture`: Instruction-set architecture used by the job-retry Lambda.
- `lambda.memory_size`: Memory allocated to the job-retry Lambda.
- `lambda.timeout`: Lambda timeout and retry-queue visibility timeout in seconds.
- `lambda.reserved_concurrent_executions`: Reserved concurrency for the Lambda. Use `-1` for unreserved concurrency.
- `lambda.environment_variables`: Additional Lambda environment variables. Required job-retry variables override matching keys.
- `lambda.vpc.subnet_ids`: Subnets used for Lambda VPC configuration.
- `lambda.vpc.security_group_ids`: Security groups used for Lambda VPC configuration.
- `lambda.role.path`: IAM path used for the job-retry Lambda role.
- `lambda.role.permissions_boundary`: Optional permissions boundary for the Lambda role.
- `lambda.role.principals`: Extra principals allowed to assume the Lambda role, for example during local testing.
- `runner.name_prefix`: Prefix used to identify runners belonging to this runner configuration.
- `github.organization_runners`: Enables organization runners.
- `github.enterprise_server.url`: Optional GitHub Enterprise Server URL.
- `github.enterprise_server.ssl_verify`: Enables TLS certificate verification for GitHub Enterprise Server requests.
- `github.user_agent`: Optional User-Agent sent to GitHub.
- `github.app_parameters.key_base64`: Parameter Store reference for the primary GitHub App private key.
- `github.app_parameters.id`: Parameter Store reference for the primary GitHub App ID.
- `github.app_parameters.additional_apps_manifest`: Optional Parameter Store reference containing the additional GitHub App manifest.
- `github.app_parameters.additional_app_parameter_arns`: ARNs of the additional GitHub App credential parameters.
- `queue.build`: URL and ARN of the build queue to which retry messages are published.
- `queue.kms_key_id`: Optional KMS key ARN used to encrypt the build queue. This is distinct from the Parameter Store key.
- `queue.event_source_mapping.batch_size`: Maximum records delivered per job-retry invocation.
- `queue.event_source_mapping.maximum_batching_window_in_seconds`: Maximum event batching window.
- `queue.encryption`: Server-side encryption configuration for the retry queue.
- `ssm.kms_key_id`: Optional KMS key ARN used by the job-retry IAM policy. Its value may be unknown until apply.
- `observability.logs`: Logging level, retention, encryption, and log-class configuration.
- `observability.tracing`: Lambda X-Ray and tracing-helper configuration.
- `observability.metrics`: Metrics enablement, namespace, and job-retry metric configuration.
- `tags.resources`: Tags for the job-retry Lambda role and component resources.
- `tags.lambda`: Tags for the job-retry Lambda function.
- `tags.log_group`: Tags for the job-retry log group.
- `tags.queue`: Tags for the retry queue.
- `tags.event_source_mapping`: Tags for the retry-queue event-source mapping. | object({
prefix = string
aws_partition = string
lambda = object({
artifact = object({
zip = string
s3 = object({
bucket = optional(string, null)
key = optional(string, null)
object_version = optional(string, null)
})
})
runtime = string
architecture = string
memory_size = number
timeout = number
reserved_concurrent_executions = number
environment_variables = map(string)
vpc = object({
subnet_ids = list(string)
security_group_ids = list(string)
})
role = object({
path = string
permissions_boundary = optional(string, null)
principals = list(object({
type = string
identifiers = list(string)
}))
})
})
runner = object({
name_prefix = string
})
github = object({
organization_runners = bool
enterprise_server = object({
url = optional(string, null)
ssl_verify = optional(bool, true)
})
user_agent = optional(string, null)
app_parameters = object({
key_base64 = map(string)
id = map(string)
additional_apps_manifest = optional(object({
name = string
arn = string
}), null)
additional_app_parameter_arns = optional(list(string), [])
})
})
queue = object({
build = object({
url = string
arn = string
})
kms_key_id = optional(string, null)
event_source_mapping = object({
batch_size = number
maximum_batching_window_in_seconds = number
})
encryption = object({
sqs_managed_sse_enabled = bool
kms_master_key_id = optional(string, null)
kms_data_key_reuse_period_seconds = optional(number, null)
})
})
ssm = object({
kms_key_id = optional(string, null)
})
observability = object({
logs = object({
level = string
retention_in_days = number
kms_key_id = optional(string, null)
class = string
})
tracing = object({
mode = optional(string, null)
capture_http_requests = bool
capture_error = bool
})
metrics = object({
enabled = bool
namespace = string
metric = object({
github_app_rate_limit = object({
enabled = bool
})
job_retry = object({
enabled = bool
})
})
})
})
tags = object({
resources = map(string)
lambda = map(string)
log_group = map(string)
queue = map(string)
event_source_mapping = map(string)
})
}) | n/a | yes |
+| [config](#input\_config) | Provider-neutral job-retry configuration assembled by runner-config.
- `prefix`: Prefix used to name job-retry resources.
- `aws_partition`: AWS partition used to construct the Lambda VPC managed-policy ARN.
- `lambda.artifact.zip`: Resolved local control-plane archive.
- `lambda.artifact.s3.bucket`: Optional S3 bucket containing the Lambda archive.
- `lambda.artifact.s3.key`: Object key of the Lambda archive.
- `lambda.artifact.s3.object_version`: Optional object version of the Lambda archive.
- `lambda.runtime`: Runtime used by the job-retry Lambda.
- `lambda.architecture`: Instruction-set architecture used by the job-retry Lambda.
- `lambda.memory_size`: Memory allocated to the job-retry Lambda.
- `lambda.timeout`: Lambda timeout and retry-queue visibility timeout in seconds.
- `lambda.reserved_concurrent_executions`: Reserved concurrency for the Lambda. Use `-1` for unreserved concurrency.
- `lambda.environment_variables`: Additional Lambda environment variables. Required job-retry variables override matching keys.
- `lambda.vpc.subnet_ids`: Subnets used for Lambda VPC configuration.
- `lambda.vpc.security_group_ids`: Security groups used for Lambda VPC configuration.
- `lambda.role.path`: IAM path used for the job-retry Lambda role.
- `lambda.role.permissions_boundary`: Optional permissions boundary for the Lambda role.
- `lambda.role.principals`: Extra principals allowed to assume the Lambda role, for example during local testing.
- `runner.name_prefix`: Prefix used to identify runners belonging to this runner configuration.
- `github.organization_runners`: Enables organization runners.
- `github.enterprise_server.url`: Optional GitHub Enterprise Server URL.
- `github.enterprise_server.ssl_verify`: Enables TLS certificate verification for GitHub Enterprise Server requests.
- `github.user_agent`: Optional User-Agent sent to GitHub.
- `github.app_parameters.key_base64`: Ordered Parameter Store references for GitHub App private keys.
- `github.app_parameters.id`: Ordered Parameter Store references for GitHub App IDs.
- `github.app_parameters.installation_id`: Ordered optional Parameter Store references for GitHub App installation IDs.
- `queue.build`: URL and ARN of the build queue to which retry messages are published.
- `queue.kms_key_id`: Optional KMS key ARN used to encrypt the build queue. This is distinct from the Parameter Store key.
- `queue.event_source_mapping.batch_size`: Maximum records delivered per job-retry invocation.
- `queue.event_source_mapping.maximum_batching_window_in_seconds`: Maximum event batching window.
- `queue.encryption`: Server-side encryption configuration for the retry queue.
- `ssm.kms_key_id`: Optional KMS key ARN used by the job-retry IAM policy. Its value may be unknown until apply.
- `observability.logs`: Logging level, retention, encryption, and log-class configuration.
- `observability.tracing`: Lambda X-Ray and tracing-helper configuration.
- `observability.metrics`: Metrics enablement, namespace, and job-retry metric configuration.
- `tags.resources`: Tags for the job-retry Lambda role and component resources.
- `tags.lambda`: Tags for the job-retry Lambda function.
- `tags.log_group`: Tags for the job-retry log group.
- `tags.queue`: Tags for the retry queue.
- `tags.event_source_mapping`: Tags for the retry-queue event-source mapping. | object({
prefix = string
aws_partition = string
lambda = object({
artifact = object({
zip = string
s3 = object({
bucket = optional(string, null)
key = optional(string, null)
object_version = optional(string, null)
})
})
runtime = string
architecture = string
memory_size = number
timeout = number
reserved_concurrent_executions = number
environment_variables = map(string)
vpc = object({
subnet_ids = list(string)
security_group_ids = list(string)
})
role = object({
path = string
permissions_boundary = optional(string, null)
principals = list(object({
type = string
identifiers = list(string)
}))
})
})
runner = object({
name_prefix = string
})
github = object({
organization_runners = bool
enterprise_server = object({
url = optional(string, null)
ssl_verify = optional(bool, true)
})
user_agent = optional(string, null)
app_parameters = object({
key_base64 = list(map(string))
id = list(map(string))
installation_id = list(object({ name = string, arn = string }))
})
})
queue = object({
build = object({
url = string
arn = string
})
kms_key_id = optional(string, null)
event_source_mapping = object({
batch_size = number
maximum_batching_window_in_seconds = number
})
encryption = object({
sqs_managed_sse_enabled = bool
kms_master_key_id = optional(string, null)
kms_data_key_reuse_period_seconds = optional(number, null)
})
})
ssm = object({
kms_key_id = optional(string, null)
})
observability = object({
logs = object({
level = string
retention_in_days = number
kms_key_id = optional(string, null)
class = string
})
tracing = object({
mode = optional(string, null)
capture_http_requests = bool
capture_error = bool
})
metrics = object({
enabled = bool
namespace = string
metric = object({
github_app_rate_limit = object({
enabled = bool
})
job_retry = object({
enabled = bool
})
})
})
})
tags = object({
resources = map(string)
lambda = map(string)
log_group = map(string)
queue = map(string)
event_source_mapping = map(string)
})
}) | n/a | yes |
## Outputs
diff --git a/modules/orchestration-providers/webhook/job-retry/iam-policies.tf b/modules/orchestration-providers/webhook/job-retry/iam-policies.tf
index dc9cecf44f..0e79e8a265 100644
--- a/modules/orchestration-providers/webhook/job-retry/iam-policies.tf
+++ b/modules/orchestration-providers/webhook/job-retry/iam-policies.tf
@@ -62,12 +62,9 @@ data "aws_iam_policy_document" "job_retry" {
]
resources = concat(
- [
- var.config.github.app_parameters.id.arn,
- var.config.github.app_parameters.key_base64.arn,
- ],
- var.config.github.app_parameters.additional_app_parameter_arns,
- var.config.github.app_parameters.additional_apps_manifest != null ? [var.config.github.app_parameters.additional_apps_manifest.arn] : [],
+ [for p in var.config.github.app_parameters.id : p.arn],
+ [for p in var.config.github.app_parameters.key_base64 : p.arn],
+ [for p in var.config.github.app_parameters.installation_id : p.arn if p != null],
)
}
diff --git a/modules/orchestration-providers/webhook/job-retry/job-retry.tf b/modules/orchestration-providers/webhook/job-retry/job-retry.tf
index 248f5c35ad..a536cfe196 100644
--- a/modules/orchestration-providers/webhook/job-retry/job-retry.tf
+++ b/modules/orchestration-providers/webhook/job-retry/job-retry.tf
@@ -19,17 +19,17 @@ locals {
}
job_retry_environment_variables = {
- ENABLE_ORGANIZATION_RUNNERS = var.config.github.organization_runners
- ENABLE_METRIC_JOB_RETRY = var.config.observability.metrics.enabled && var.config.observability.metrics.metric.job_retry.enabled
- ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.config.observability.metrics.enabled && var.config.observability.metrics.metric.github_app_rate_limit.enabled
- GHES_URL = var.config.github.enterprise_server.url
- NODE_TLS_REJECT_UNAUTHORIZED = var.config.github.enterprise_server.url != null && !var.config.github.enterprise_server.ssl_verify ? 0 : 1
- USER_AGENT = var.config.github.user_agent
- JOB_QUEUE_SCALE_UP_URL = var.config.queue.build.url
- PARAMETER_GITHUB_APP_ID_NAME = var.config.github.app_parameters.id.name
- PARAMETER_GITHUB_APP_KEY_BASE64_NAME = var.config.github.app_parameters.key_base64.name
- PARAMETER_GITHUB_APPS_MANIFEST_NAME = var.config.github.app_parameters.additional_apps_manifest != null ? var.config.github.app_parameters.additional_apps_manifest.name : ""
- RUNNER_NAME_PREFIX = var.config.runner.name_prefix
+ ENABLE_ORGANIZATION_RUNNERS = var.config.github.organization_runners
+ ENABLE_METRIC_JOB_RETRY = var.config.observability.metrics.enabled && var.config.observability.metrics.metric.job_retry.enabled
+ ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.config.observability.metrics.enabled && var.config.observability.metrics.metric.github_app_rate_limit.enabled
+ GHES_URL = var.config.github.enterprise_server.url
+ NODE_TLS_REJECT_UNAUTHORIZED = var.config.github.enterprise_server.url != null && !var.config.github.enterprise_server.ssl_verify ? 0 : 1
+ USER_AGENT = var.config.github.user_agent
+ JOB_QUEUE_SCALE_UP_URL = var.config.queue.build.url
+ PARAMETER_GITHUB_APP_ID_NAME = join(":", [for p in var.config.github.app_parameters.id : p.name])
+ PARAMETER_GITHUB_APP_KEY_BASE64_NAME = join(":", [for p in var.config.github.app_parameters.key_base64 : p.name])
+ PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME = join(":", [for p in var.config.github.app_parameters.installation_id : p != null ? p.name : ""])
+ RUNNER_NAME_PREFIX = var.config.runner.name_prefix
}
environment_variables = merge(
diff --git a/modules/orchestration-providers/webhook/job-retry/tests/job-retry.tftest.hcl b/modules/orchestration-providers/webhook/job-retry/tests/job-retry.tftest.hcl
index 7d07bca573..25d1dfaafc 100644
--- a/modules/orchestration-providers/webhook/job-retry/tests/job-retry.tftest.hcl
+++ b/modules/orchestration-providers/webhook/job-retry/tests/job-retry.tftest.hcl
@@ -57,22 +57,32 @@ variables {
}
user_agent = "experimental-job-retry-user-agent"
app_parameters = {
- key_base64 = {
- name = "/github-runner/key-base64"
- arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64"
- }
- id = {
- name = "/github-runner/app-id"
- arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id"
- }
- additional_apps_manifest = {
- name = "/github-runner/additional-apps-manifest"
- arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/additional-apps-manifest"
- }
- additional_app_parameter_arns = [
- "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id-2",
- "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64-2",
- "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/installation-id-2",
+ key_base64 = [
+ {
+ name = "/github-runner/key-base64"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64"
+ },
+ {
+ name = "/github-runner/key-base64-2"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64-2"
+ },
+ ]
+ id = [
+ {
+ name = "/github-runner/app-id"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id"
+ },
+ {
+ name = "/github-runner/app-id-2"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id-2"
+ },
+ ]
+ installation_id = [
+ null,
+ {
+ name = "/github-runner/installation-id-2"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/installation-id-2"
+ },
]
}
}
@@ -145,15 +155,14 @@ run "preserves_nested_job_retry_configuration" {
output.lambda.function.environment[0].variables["GHES_URL"] == "https://experimental-job-retry.example.com"
&& output.lambda.function.environment[0].variables["NODE_TLS_REJECT_UNAUTHORIZED"] == "0"
&& output.lambda.function.environment[0].variables["USER_AGENT"] == "experimental-job-retry-user-agent"
- && output.lambda.function.environment[0].variables["PARAMETER_GITHUB_APP_ID_NAME"] == "/github-runner/app-id"
- && output.lambda.function.environment[0].variables["PARAMETER_GITHUB_APP_KEY_BASE64_NAME"] == "/github-runner/key-base64"
- && output.lambda.function.environment[0].variables["PARAMETER_GITHUB_APPS_MANIFEST_NAME"] == "/github-runner/additional-apps-manifest"
+ && output.lambda.function.environment[0].variables["PARAMETER_GITHUB_APP_ID_NAME"] == "/github-runner/app-id:/github-runner/app-id-2"
+ && output.lambda.function.environment[0].variables["PARAMETER_GITHUB_APP_KEY_BASE64_NAME"] == "/github-runner/key-base64:/github-runner/key-base64-2"
+ && output.lambda.function.environment[0].variables["PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME"] == ":/github-runner/installation-id-2"
&& contains(data.aws_iam_policy_document.job_retry.statement[0].resources, "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id-2")
&& contains(data.aws_iam_policy_document.job_retry.statement[0].resources, "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64-2")
&& contains(data.aws_iam_policy_document.job_retry.statement[0].resources, "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/installation-id-2")
- && contains(data.aws_iam_policy_document.job_retry.statement[0].resources, "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/additional-apps-manifest")
)
- error_message = "Job retry must receive the new GitHub App parameter format and grant access to every corresponding SSM ARN."
+ error_message = "Job retry must receive the nested GitHub connection settings, pass every app parameter, and grant access to every corresponding SSM ARN."
}
assert {
@@ -258,14 +267,15 @@ run "does_not_enable_partial_vpc_configuration" {
organization_runners = false
enterprise_server = {}
app_parameters = {
- key_base64 = {
+ key_base64 = [{
name = "/github-runner/key-base64"
arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64"
- }
- id = {
+ }]
+ id = [{
name = "/github-runner/app-id"
arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id"
- }
+ }]
+ installation_id = [null]
}
}
queue = {
diff --git a/modules/orchestration-providers/webhook/job-retry/variables.tf b/modules/orchestration-providers/webhook/job-retry/variables.tf
index 9b2c2610ab..e8235265f8 100644
--- a/modules/orchestration-providers/webhook/job-retry/variables.tf
+++ b/modules/orchestration-providers/webhook/job-retry/variables.tf
@@ -24,10 +24,9 @@ variable "config" {
- `github.enterprise_server.url`: Optional GitHub Enterprise Server URL.
- `github.enterprise_server.ssl_verify`: Enables TLS certificate verification for GitHub Enterprise Server requests.
- `github.user_agent`: Optional User-Agent sent to GitHub.
- - `github.app_parameters.key_base64`: Parameter Store reference for the primary GitHub App private key.
- - `github.app_parameters.id`: Parameter Store reference for the primary GitHub App ID.
- - `github.app_parameters.additional_apps_manifest`: Optional Parameter Store reference containing the additional GitHub App manifest.
- - `github.app_parameters.additional_app_parameter_arns`: ARNs of the additional GitHub App credential parameters.
+ - `github.app_parameters.key_base64`: Ordered Parameter Store references for GitHub App private keys.
+ - `github.app_parameters.id`: Ordered Parameter Store references for GitHub App IDs.
+ - `github.app_parameters.installation_id`: Ordered optional Parameter Store references for GitHub App installation IDs.
- `queue.build`: URL and ARN of the build queue to which retry messages are published.
- `queue.kms_key_id`: Optional KMS key ARN used to encrypt the build queue. This is distinct from the Parameter Store key.
- `queue.event_source_mapping.batch_size`: Maximum records delivered per job-retry invocation.
@@ -86,13 +85,9 @@ variable "config" {
})
user_agent = optional(string, null)
app_parameters = object({
- key_base64 = map(string)
- id = map(string)
- additional_apps_manifest = optional(object({
- name = string
- arn = string
- }), null)
- additional_app_parameter_arns = optional(list(string), [])
+ key_base64 = list(map(string))
+ id = list(map(string))
+ installation_id = list(object({ name = string, arn = string }))
})
})
queue = object({
diff --git a/modules/orchestration-providers/webhook/job-retry/versions.tf b/modules/orchestration-providers/webhook/job-retry/versions.tf
index 1238b79cc3..fcec7c620d 100644
--- a/modules/orchestration-providers/webhook/job-retry/versions.tf
+++ b/modules/orchestration-providers/webhook/job-retry/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.4.0"
required_providers {
aws = {
diff --git a/modules/orchestration-providers/webhook/pool/README.md b/modules/orchestration-providers/webhook/pool/README.md
index 03a9c0d15d..877eec8039 100644
--- a/modules/orchestration-providers/webhook/pool/README.md
+++ b/modules/orchestration-providers/webhook/pool/README.md
@@ -10,7 +10,7 @@ The pool is an opt-in feature. To be able to use the count on a module level to
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.4.0 |
| [aws](#requirement\_aws) | >= 6.21 |
## Providers
@@ -54,7 +54,7 @@ No modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| [aws\_partition](#input\_aws\_partition) | (optional) partition for the arn if not 'aws' | `string` | `"aws"` | no |
-| [config](#input\_config) | Configuration passed from the webhook orchestration provider to the pool Lambda and scheduler.
- `lambda`: Pool Lambda runtime and deployment configuration.
- `lambda.log_level`: Logging level used by the pool Lambda.
- `lambda.logging_retention_in_days`: Number of days to retain events in the pool Lambda log group.
- `lambda.logging_kms_key_id`: KMS key ID used to encrypt the pool Lambda log group.
- `lambda.log_class`: CloudWatch Logs class for the pool Lambda log group.
- `lambda.reserved_concurrent_executions`: Reserved concurrency for the pool Lambda. Use -1 for no reservation.
- `lambda.s3_bucket`: S3 bucket containing the pool Lambda deployment package.
- `lambda.s3_key`: S3 key of the pool Lambda deployment package.
- `lambda.s3_object_version`: S3 object version of the pool Lambda deployment package.
- `lambda.security_group_ids`: Security group IDs associated with the pool Lambda.
- `lambda.runtime`: AWS Lambda runtime used by the pool Lambda.
- `lambda.architecture`: AWS Lambda architecture used by the pool Lambda.
- `lambda.memory_size`: Memory allocated to the pool Lambda in MB.
- `lambda.timeout`: Pool Lambda timeout in seconds.
- `lambda.zip`: Local path to the pool Lambda deployment package when S3 is not used.
- `lambda.subnet_ids`: Subnet IDs in which the pool Lambda runs.
- `lambda.parameter_store_tags`: JSON-encoded tags supplied to the pool Lambda for SSM parameters it creates.
- `lambda.principals`: Additional principals allowed to assume the pool Lambda role.
- `tags`: Common tags added to pool resources.
- `ghes`: GitHub Enterprise Server connection configuration.
- `ghes.url`: GitHub Enterprise Server URL; null when using public GitHub.
- `ghes.ssl_verify`: Whether the pool Lambda verifies the GitHub Enterprise Server TLS certificate.
- `github_app_parameters`: SSM parameter metadata for the primary and additional GitHub App credentials.
- `github_app_parameters.key_base64`: Parameter Store reference for the primary GitHub App private key.
- `github_app_parameters.id`: Parameter Store reference for the primary GitHub App ID.
- `github_app_parameters.additional_apps_manifest`: Optional Parameter Store reference containing the additional GitHub App manifest.
- `github_app_parameters.additional_app_parameter_arns`: ARNs of the additional GitHub App credential parameters.
- `runner`: Runner registration configuration used by the pool Lambda.
- `runner.disable_runner_autoupdate`: Whether GitHub runner automatic updates are disabled.
- `runner.ephemeral`: Whether runners register as ephemeral runners.
- `runner.enable_jit_config`: Whether runners use just-in-time registration configuration.
- `runner.labels`: Labels assigned to runners created by the pool Lambda.
- `runner.group_name`: GitHub runner group assigned to runners created by the pool Lambda.
- `runner.name_prefix`: Prefix used for runner names.
- `runner.pool_owner`: GitHub organization or repository that owns the runner pool.
- `runner.boot_time_in_minutes`: Webhook-provider runner boot timeout used by pool reconciliation.
- `runners_maximum_count`: Webhook-provider runner capacity limit enforced by the pool Lambda.
- `prefix`: Prefix used to name pool resources.
- `pool`: Scheduled pool targets.
- `pool[*].schedule_expression`: EventBridge Scheduler expression for a pool target.
- `pool[*].schedule_expression_timezone`: Time zone used to evaluate the schedule expression.
- `pool[*].size`: Desired runner count for the scheduled pool target.
- `include_busy_runners`: Whether busy runners count toward the desired pool size.
- `role_permissions_boundary`: Permissions boundary applied to IAM roles created for the pool.
- `kms_key_id`: Optional customer-managed KMS key ARN that the pool Lambda may use to decrypt encrypted parameters.
- `role_path`: IAM path applied to roles created for the pool.
- `ssm_token_path`: SSM path under which runner registration tokens are stored.
- `ssm_token_path_arn`: ARN matching the runner registration-token SSM path.
- `ssm_config_path`: SSM path under which runner configuration is stored.
- `arn_ssm_parameters_path_config`: ARN matching the runner configuration SSM path.
- `lambda_tags`: Tags added specifically to the pool Lambda function, overriding common tags with the same key.
- `log_group_tags`: Tags added specifically to the pool Lambda log group, overriding common tags with the same key.
- `user_agent`: User-Agent header used for GitHub API requests. | object({
lambda = object({
log_level = string
logging_retention_in_days = number
logging_kms_key_id = string
log_class = string
reserved_concurrent_executions = number
s3_bucket = string
s3_key = string
s3_object_version = string
security_group_ids = list(string)
runtime = string
architecture = string
memory_size = number
timeout = number
zip = string
subnet_ids = list(string)
parameter_store_tags = string
principals = optional(list(object({
type = string
identifiers = list(string)
})), [])
})
tags = map(string)
ghes = object({
url = string
ssl_verify = string
})
github_app_parameters = object({
key_base64 = map(string)
id = map(string)
additional_apps_manifest = optional(object({
name = string
arn = string
}), null)
additional_app_parameter_arns = optional(list(string), [])
})
runner = object({
disable_runner_autoupdate = bool
ephemeral = bool
enable_jit_config = bool
labels = list(string)
group_name = string
name_prefix = string
pool_owner = string
boot_time_in_minutes = number
})
runners_maximum_count = number
prefix = string
pool = list(object({
schedule_expression = string
schedule_expression_timezone = string
size = number
}))
include_busy_runners = bool
role_permissions_boundary = string
kms_key_id = optional(string, null)
role_path = string
ssm_token_path = string
ssm_token_path_arn = string
ssm_config_path = string
arn_ssm_parameters_path_config = string
lambda_tags = map(string)
log_group_tags = optional(map(string), {})
user_agent = string
}) | n/a | yes |
+| [config](#input\_config) | Configuration passed from the webhook orchestration provider to the pool Lambda and scheduler.
- `lambda`: Pool Lambda runtime and deployment configuration.
- `lambda.log_level`: Logging level used by the pool Lambda.
- `lambda.logging_retention_in_days`: Number of days to retain events in the pool Lambda log group.
- `lambda.logging_kms_key_id`: KMS key ID used to encrypt the pool Lambda log group.
- `lambda.log_class`: CloudWatch Logs class for the pool Lambda log group.
- `lambda.reserved_concurrent_executions`: Reserved concurrency for the pool Lambda. Use -1 for no reservation.
- `lambda.s3_bucket`: S3 bucket containing the pool Lambda deployment package.
- `lambda.s3_key`: S3 key of the pool Lambda deployment package.
- `lambda.s3_object_version`: S3 object version of the pool Lambda deployment package.
- `lambda.security_group_ids`: Security group IDs associated with the pool Lambda.
- `lambda.runtime`: AWS Lambda runtime used by the pool Lambda.
- `lambda.architecture`: AWS Lambda architecture used by the pool Lambda.
- `lambda.memory_size`: Memory allocated to the pool Lambda in MB.
- `lambda.timeout`: Pool Lambda timeout in seconds.
- `lambda.zip`: Local path to the pool Lambda deployment package when S3 is not used.
- `lambda.subnet_ids`: Subnet IDs in which the pool Lambda runs.
- `lambda.parameter_store_tags`: JSON-encoded tags supplied to the pool Lambda for SSM parameters it creates.
- `lambda.principals`: Additional principals allowed to assume the pool Lambda role.
- `tags`: Common tags added to pool resources.
- `ghes`: GitHub Enterprise Server connection configuration.
- `ghes.url`: GitHub Enterprise Server URL; null when using public GitHub.
- `ghes.ssl_verify`: Whether the pool Lambda verifies the GitHub Enterprise Server TLS certificate.
- `github_app_parameters`: Ordered SSM parameter metadata for GitHub App credentials.
- `github_app_parameters.key_base64`: Ordered Parameter Store references for GitHub App private keys.
- `github_app_parameters.id`: Ordered Parameter Store references for GitHub App IDs.
- `github_app_parameters.installation_id`: Ordered optional Parameter Store references for GitHub App installation IDs.
- `runner`: Runner registration configuration used by the pool Lambda.
- `runner.disable_runner_autoupdate`: Whether GitHub runner automatic updates are disabled.
- `runner.ephemeral`: Whether runners register as ephemeral runners.
- `runner.enable_jit_config`: Whether runners use just-in-time registration configuration.
- `runner.labels`: Labels assigned to runners created by the pool Lambda.
- `runner.group_name`: GitHub runner group assigned to runners created by the pool Lambda.
- `runner.name_prefix`: Prefix used for runner names.
- `runner.pool_owner`: GitHub organization or repository that owns the runner pool.
- `runner.boot_time_in_minutes`: Webhook-provider runner boot timeout used by pool reconciliation.
- `runners_maximum_count`: Webhook-provider runner capacity limit enforced by the pool Lambda.
- `prefix`: Prefix used to name pool resources.
- `pool`: Scheduled pool targets.
- `pool[*].schedule_expression`: EventBridge Scheduler expression for a pool target.
- `pool[*].schedule_expression_timezone`: Time zone used to evaluate the schedule expression.
- `pool[*].size`: Desired runner count for the scheduled pool target.
- `include_busy_runners`: Whether busy runners count toward the desired pool size.
- `role_permissions_boundary`: Permissions boundary applied to IAM roles created for the pool.
- `kms_key_id`: Optional customer-managed KMS key ARN that the pool Lambda may use to decrypt encrypted parameters.
- `role_path`: IAM path applied to roles created for the pool.
- `ssm_token_path`: SSM path under which runner registration tokens are stored.
- `ssm_token_path_arn`: ARN matching the runner registration-token SSM path.
- `ssm_config_path`: SSM path under which runner configuration is stored.
- `arn_ssm_parameters_path_config`: ARN matching the runner configuration SSM path.
- `lambda_tags`: Tags added specifically to the pool Lambda function, overriding common tags with the same key.
- `log_group_tags`: Tags added specifically to the pool Lambda log group, overriding common tags with the same key.
- `user_agent`: User-Agent header used for GitHub API requests. | object({
lambda = object({
log_level = string
logging_retention_in_days = number
logging_kms_key_id = string
log_class = string
reserved_concurrent_executions = number
s3_bucket = string
s3_key = string
s3_object_version = string
security_group_ids = list(string)
runtime = string
architecture = string
memory_size = number
timeout = number
zip = string
subnet_ids = list(string)
parameter_store_tags = string
principals = optional(list(object({
type = string
identifiers = list(string)
})), [])
})
tags = map(string)
ghes = object({
url = string
ssl_verify = string
})
github_app_parameters = object({
key_base64 = list(map(string))
id = list(map(string))
installation_id = list(object({ name = string, arn = string }))
})
runner = object({
disable_runner_autoupdate = bool
ephemeral = bool
enable_jit_config = bool
labels = list(string)
group_name = string
name_prefix = string
pool_owner = string
boot_time_in_minutes = number
})
runners_maximum_count = number
prefix = string
pool = list(object({
schedule_expression = string
schedule_expression_timezone = string
size = number
}))
include_busy_runners = bool
role_permissions_boundary = string
kms_key_id = optional(string, null)
role_path = string
ssm_token_path = string
ssm_token_path_arn = string
ssm_config_path = string
arn_ssm_parameters_path_config = string
lambda_tags = map(string)
log_group_tags = optional(map(string), {})
user_agent = string
}) | n/a | yes |
| [runner\_provider](#input\_runner\_provider) | Compute provider integration used by the pool Lambda.
- `type`: Compute provider type passed to scheduled pool invocations.
- `environment_variables`: Provider-specific environment variables added to the pool Lambda.
- `iam_policy_json`: Provider-specific IAM policy document merged into the pool Lambda policy.
- `managed_policy_enabled`: Whether to attach a provider-specific managed IAM policy to the pool Lambda role.
- `managed_policy_arn`: ARN of the provider-specific managed IAM policy to attach when enabled. | object({
type = string
environment_variables = map(string)
iam_policy_json = string
managed_policy_enabled = bool
managed_policy_arn = optional(string, null)
}) | n/a | yes |
| [tracing\_config](#input\_tracing\_config) | Tracing configuration for the pool Lambda.
- `mode`: AWS X-Ray tracing mode. A null value disables tracing.
- `capture_http_requests`: Whether Powertools tracing captures outgoing HTTP requests.
- `capture_error`: Whether Powertools tracing captures errors as tracing metadata. | object({
mode = optional(string, null)
capture_http_requests = optional(bool, false)
capture_error = optional(bool, false)
}) | `{}` | no |
diff --git a/modules/orchestration-providers/webhook/pool/iam-policies.tf b/modules/orchestration-providers/webhook/pool/iam-policies.tf
index bd5c15a25d..f5a9285bce 100644
--- a/modules/orchestration-providers/webhook/pool/iam-policies.tf
+++ b/modules/orchestration-providers/webhook/pool/iam-policies.tf
@@ -43,12 +43,9 @@ data "aws_iam_policy_document" "pool_common" {
]
resources = concat(
- [
- var.config.github_app_parameters.id.arn,
- var.config.github_app_parameters.key_base64.arn,
- ],
- var.config.github_app_parameters.additional_app_parameter_arns,
- var.config.github_app_parameters.additional_apps_manifest != null ? [var.config.github_app_parameters.additional_apps_manifest.arn] : [],
+ [for p in var.config.github_app_parameters.id : p.arn],
+ [for p in var.config.github_app_parameters.key_base64 : p.arn],
+ [for p in var.config.github_app_parameters.installation_id : p.arn if p != null],
)
}
diff --git a/modules/orchestration-providers/webhook/pool/pool.tf b/modules/orchestration-providers/webhook/pool/pool.tf
index 00e12dd433..cff2776e90 100644
--- a/modules/orchestration-providers/webhook/pool/pool.tf
+++ b/modules/orchestration-providers/webhook/pool/pool.tf
@@ -7,32 +7,32 @@ locals {
)
common_environment_variables = {
- DISABLE_RUNNER_AUTOUPDATE = var.config.runner.disable_runner_autoupdate
- ENABLE_EPHEMERAL_RUNNERS = var.config.runner.ephemeral
- ENABLE_JIT_CONFIG = var.config.runner.enable_jit_config
- ENVIRONMENT = var.config.prefix
- GHES_URL = var.config.ghes.url
- USER_AGENT = var.config.user_agent
- LOG_LEVEL = upper(var.config.lambda.log_level)
- NODE_TLS_REJECT_UNAUTHORIZED = var.config.ghes.url != null && !var.config.ghes.ssl_verify ? 0 : 1
- PARAMETER_GITHUB_APP_ID_NAME = var.config.github_app_parameters.id.name
- PARAMETER_GITHUB_APP_KEY_BASE64_NAME = var.config.github_app_parameters.key_base64.name
- PARAMETER_GITHUB_APPS_MANIFEST_NAME = var.config.github_app_parameters.additional_apps_manifest != null ? var.config.github_app_parameters.additional_apps_manifest.name : ""
- POWERTOOLS_LOGGER_LOG_EVENT = var.config.lambda.log_level == "debug" ? "true" : "false"
- RUNNER_LABELS = lower(join(",", var.config.runner.labels))
- RUNNER_GROUP_NAME = var.config.runner.group_name
- RUNNER_NAME_PREFIX = var.config.runner.name_prefix
- RUNNER_OWNER = var.config.runner.pool_owner
- RUNNER_BOOT_TIME_IN_MINUTES = var.config.runner.boot_time_in_minutes
- RUNNERS_MAXIMUM_COUNT = var.config.runners_maximum_count
- SSM_TOKEN_PATH = var.config.ssm_token_path
- SSM_CONFIG_PATH = var.config.ssm_config_path
- POWERTOOLS_SERVICE_NAME = "${var.config.prefix}-pool"
- POWERTOOLS_TRACE_ENABLED = var.tracing_config.mode != null ? true : false
- POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.tracing_config.capture_http_requests
- POWERTOOLS_TRACER_CAPTURE_ERROR = var.tracing_config.capture_error
- SSM_PARAMETER_STORE_TAGS = var.config.lambda.parameter_store_tags
- INCLUDE_BUSY_RUNNERS = var.config.include_busy_runners
+ DISABLE_RUNNER_AUTOUPDATE = var.config.runner.disable_runner_autoupdate
+ ENABLE_EPHEMERAL_RUNNERS = var.config.runner.ephemeral
+ ENABLE_JIT_CONFIG = var.config.runner.enable_jit_config
+ ENVIRONMENT = var.config.prefix
+ GHES_URL = var.config.ghes.url
+ USER_AGENT = var.config.user_agent
+ LOG_LEVEL = upper(var.config.lambda.log_level)
+ NODE_TLS_REJECT_UNAUTHORIZED = var.config.ghes.url != null && !var.config.ghes.ssl_verify ? 0 : 1
+ PARAMETER_GITHUB_APP_ID_NAME = join(":", [for p in var.config.github_app_parameters.id : p.name])
+ PARAMETER_GITHUB_APP_KEY_BASE64_NAME = join(":", [for p in var.config.github_app_parameters.key_base64 : p.name])
+ PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME = join(":", [for p in var.config.github_app_parameters.installation_id : p != null ? p.name : ""])
+ POWERTOOLS_LOGGER_LOG_EVENT = var.config.lambda.log_level == "debug" ? "true" : "false"
+ RUNNER_LABELS = lower(join(",", var.config.runner.labels))
+ RUNNER_GROUP_NAME = var.config.runner.group_name
+ RUNNER_NAME_PREFIX = var.config.runner.name_prefix
+ RUNNER_OWNER = var.config.runner.pool_owner
+ RUNNER_BOOT_TIME_IN_MINUTES = var.config.runner.boot_time_in_minutes
+ RUNNERS_MAXIMUM_COUNT = var.config.runners_maximum_count
+ SSM_TOKEN_PATH = var.config.ssm_token_path
+ SSM_CONFIG_PATH = var.config.ssm_config_path
+ POWERTOOLS_SERVICE_NAME = "${var.config.prefix}-pool"
+ POWERTOOLS_TRACE_ENABLED = var.tracing_config.mode != null ? true : false
+ POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.tracing_config.capture_http_requests
+ POWERTOOLS_TRACER_CAPTURE_ERROR = var.tracing_config.capture_error
+ SSM_PARAMETER_STORE_TAGS = var.config.lambda.parameter_store_tags
+ INCLUDE_BUSY_RUNNERS = var.config.include_busy_runners
}
}
diff --git a/modules/orchestration-providers/webhook/pool/tests/provider.tftest.hcl b/modules/orchestration-providers/webhook/pool/tests/provider.tftest.hcl
index 4b5035a58f..c04f435024 100644
--- a/modules/orchestration-providers/webhook/pool/tests/provider.tftest.hcl
+++ b/modules/orchestration-providers/webhook/pool/tests/provider.tftest.hcl
@@ -38,22 +38,32 @@ variables {
ssl_verify = true
}
github_app_parameters = {
- key_base64 = {
- name = "/github-runner/key-base64"
- arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64"
- }
- id = {
- name = "/github-runner/app-id"
- arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id"
- }
- additional_apps_manifest = {
- name = "/github-runner/additional-apps-manifest"
- arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/additional-apps-manifest"
- }
- additional_app_parameter_arns = [
- "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id-2",
- "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64-2",
- "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/installation-id-2",
+ key_base64 = [
+ {
+ name = "/github-runner/key-base64"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64"
+ },
+ {
+ name = "/github-runner/key-base64-2"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64-2"
+ },
+ ]
+ id = [
+ {
+ name = "/github-runner/app-id"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id"
+ },
+ {
+ name = "/github-runner/app-id-2"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id-2"
+ },
+ ]
+ installation_id = [
+ null,
+ {
+ name = "/github-runner/installation-id-2"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/installation-id-2"
+ },
]
}
runner = {
@@ -136,15 +146,14 @@ run "provider_supplies_only_compute_specific_pool_configuration" {
assert {
condition = (
- aws_lambda_function.pool.environment[0].variables["PARAMETER_GITHUB_APP_ID_NAME"] == "/github-runner/app-id"
- && aws_lambda_function.pool.environment[0].variables["PARAMETER_GITHUB_APP_KEY_BASE64_NAME"] == "/github-runner/key-base64"
- && aws_lambda_function.pool.environment[0].variables["PARAMETER_GITHUB_APPS_MANIFEST_NAME"] == "/github-runner/additional-apps-manifest"
+ aws_lambda_function.pool.environment[0].variables["PARAMETER_GITHUB_APP_ID_NAME"] == "/github-runner/app-id:/github-runner/app-id-2"
+ && aws_lambda_function.pool.environment[0].variables["PARAMETER_GITHUB_APP_KEY_BASE64_NAME"] == "/github-runner/key-base64:/github-runner/key-base64-2"
+ && aws_lambda_function.pool.environment[0].variables["PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME"] == ":/github-runner/installation-id-2"
&& contains(data.aws_iam_policy_document.pool_common.statement[2].resources, "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id-2")
&& contains(data.aws_iam_policy_document.pool_common.statement[2].resources, "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64-2")
&& contains(data.aws_iam_policy_document.pool_common.statement[2].resources, "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/installation-id-2")
- && contains(data.aws_iam_policy_document.pool_common.statement[2].resources, "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/additional-apps-manifest")
)
- error_message = "Pool must receive the new GitHub App parameter format and grant access to every corresponding SSM ARN."
+ error_message = "Pool must pass every GitHub App parameter and grant access to every corresponding SSM ARN."
}
assert {
diff --git a/modules/orchestration-providers/webhook/pool/variables.tf b/modules/orchestration-providers/webhook/pool/variables.tf
index d4c07fee63..e1f516c8ad 100644
--- a/modules/orchestration-providers/webhook/pool/variables.tf
+++ b/modules/orchestration-providers/webhook/pool/variables.tf
@@ -24,11 +24,10 @@ variable "config" {
- `ghes`: GitHub Enterprise Server connection configuration.
- `ghes.url`: GitHub Enterprise Server URL; null when using public GitHub.
- `ghes.ssl_verify`: Whether the pool Lambda verifies the GitHub Enterprise Server TLS certificate.
- - `github_app_parameters`: SSM parameter metadata for the primary and additional GitHub App credentials.
- - `github_app_parameters.key_base64`: Parameter Store reference for the primary GitHub App private key.
- - `github_app_parameters.id`: Parameter Store reference for the primary GitHub App ID.
- - `github_app_parameters.additional_apps_manifest`: Optional Parameter Store reference containing the additional GitHub App manifest.
- - `github_app_parameters.additional_app_parameter_arns`: ARNs of the additional GitHub App credential parameters.
+ - `github_app_parameters`: Ordered SSM parameter metadata for GitHub App credentials.
+ - `github_app_parameters.key_base64`: Ordered Parameter Store references for GitHub App private keys.
+ - `github_app_parameters.id`: Ordered Parameter Store references for GitHub App IDs.
+ - `github_app_parameters.installation_id`: Ordered optional Parameter Store references for GitHub App installation IDs.
- `runner`: Runner registration configuration used by the pool Lambda.
- `runner.disable_runner_autoupdate`: Whether GitHub runner automatic updates are disabled.
- `runner.ephemeral`: Whether runners register as ephemeral runners.
@@ -85,13 +84,9 @@ variable "config" {
ssl_verify = string
})
github_app_parameters = object({
- key_base64 = map(string)
- id = map(string)
- additional_apps_manifest = optional(object({
- name = string
- arn = string
- }), null)
- additional_app_parameter_arns = optional(list(string), [])
+ key_base64 = list(map(string))
+ id = list(map(string))
+ installation_id = list(object({ name = string, arn = string }))
})
runner = object({
disable_runner_autoupdate = bool
diff --git a/modules/orchestration-providers/webhook/pool/versions.tf b/modules/orchestration-providers/webhook/pool/versions.tf
index 1238b79cc3..fcec7c620d 100644
--- a/modules/orchestration-providers/webhook/pool/versions.tf
+++ b/modules/orchestration-providers/webhook/pool/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.4.0"
required_providers {
aws = {
diff --git a/modules/orchestration-providers/webhook/scale-runners.tf b/modules/orchestration-providers/webhook/scale-runners.tf
index 4863436b7c..caf79eefb5 100644
--- a/modules/orchestration-providers/webhook/scale-runners.tf
+++ b/modules/orchestration-providers/webhook/scale-runners.tf
@@ -38,7 +38,6 @@ module "scale_runners" {
}
})
scale_down = merge(local.resolved_config.scale_down, {
- idle_confirmation_seconds = local.resolved_config.scale_down.idle_confirmation_seconds
tags = {
resources = local.scale_down_tags
lambda = local.scale_down_lambda_tags
diff --git a/modules/orchestration-providers/webhook/scale-runners/README.md b/modules/orchestration-providers/webhook/scale-runners/README.md
index eee03f94da..3b096f9b85 100644
--- a/modules/orchestration-providers/webhook/scale-runners/README.md
+++ b/modules/orchestration-providers/webhook/scale-runners/README.md
@@ -11,7 +11,7 @@ The module is an implementation detail of the experimental runner configuration.
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.4.0 |
| [aws](#requirement\_aws) | >= 6.33 |
## Providers
@@ -67,7 +67,7 @@ No modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| [aws\_partition](#input\_aws\_partition) | AWS partition used to construct IAM policy ARNs. | `string` | `"aws"` | no |
-| [config](#input\_config) | Provider-neutral scale-up and scale-down configuration assembled by runner-config.
- `prefix`: Prefix used to name scaling resources.
- `lambda.artifact.zip`: Resolved local control-plane archive.
- `lambda.artifact.s3.bucket`: Optional S3 bucket containing the Lambda archive.
- `lambda.artifact.s3.key`: Object key of the Lambda archive.
- `lambda.artifact.s3.object_version`: Optional object version of the Lambda archive.
- `lambda.runtime`: Runtime used by both scaling Lambdas.
- `lambda.architecture`: Instruction-set architecture used by both scaling Lambdas.
- `lambda.vpc.subnet_ids`: Subnets used for Lambda VPC configuration.
- `lambda.vpc.security_group_ids`: Security groups used for Lambda VPC configuration.
- `lambda.role.path`: IAM path used for the scaling Lambda roles.
- `lambda.role.permissions_boundary`: Optional permissions boundary for the scaling Lambda roles.
- `lambda.role.principals`: Additional principals allowed to assume the scaling Lambda roles.
- `runner.os`: Runner operating system used for the minimum-runtime default.
- `runner.auto_update_disabled`: Disables the GitHub runner application's built-in updater.
- `runner.ephemeral`: Registers runners in ephemeral mode.
- `runner.jit_config_enabled`: Enables or disables just-in-time runner configuration.
- `runner.labels`: Labels supplied when a runner is registered.
- `runner.group_name`: GitHub runner group used during registration.
- `runner.name_prefix`: Prefix added to registered runner names.
- `runner.boot_time_in_minutes`: Webhook-provider runner boot timeout used by scale-down.
- `runner.maximum_count`: Webhook-provider runner capacity limit for this runner configuration.
- `github.organization_runners`: Registers organization runners when true.
- `github.enterprise_server.url`: Optional GitHub Enterprise Server URL.
- `github.enterprise_server.ssl_verify`: Enables TLS verification for GitHub Enterprise Server.
- `github.user_agent`: Optional User-Agent sent to GitHub.
- `github.app_parameters.key_base64`: Parameter Store reference for the primary GitHub App private key.
- `github.app_parameters.id`: Parameter Store reference for the primary GitHub App ID.
- `github.app_parameters.additional_apps_manifest`: Optional Parameter Store reference containing the additional GitHub App manifest.
- `github.app_parameters.additional_app_parameter_arns`: ARNs of the additional GitHub App credential parameters.
- `queue.build.arn`: ARN of the build queue consumed by scale-up.
- `queue.kms_key_id`: Optional KMS key ARN used to encrypt the build queue. This is distinct from the Parameter Store key.
- `queue.event_source_mapping.batch_size`: Maximum records delivered per scale-up invocation.
- `queue.event_source_mapping.maximum_batching_window_in_seconds`: Maximum event batching window.
- `ssm.token_path`: Parameter Store path used for registration tokens.
- `ssm.token_path_arn`: ARN of the Parameter Store path used for registration tokens.
- `ssm.config_path`: Parameter Store path used for persistent runner configuration.
- `ssm.config_path_arn`: ARN of the persistent runner configuration path.
- `ssm.kms_key_id`: Optional KMS key ARN used to decrypt shared parameters. Its value may be unknown until apply.
- `ssm.parameter_store_tags`: JSON-encoded tags applied to parameters created at runtime.
- `observability.logs`: Shared logging level, retention, encryption, and log-class configuration.
- `observability.tracing`: Lambda X-Ray and tracing-helper configuration.
- `observability.metrics`: Metrics enablement, namespace, and GitHub rate-limit metric configuration.
- `scale_up`: Scale-up Lambda sizing, concurrency, queued-job behavior, and resolved resource tag maps.
- `scale_up.tags.resources`: Tags for the scale-up IAM role and other component resources.
- `scale_up.tags.lambda`: Tags for the scale-up Lambda function.
- `scale_up.tags.log_group`: Tags for the scale-up log group.
- `scale_up.tags.event_source_mapping`: Tags for the build-queue event-source mapping.
- `scale_down`: Scale-down Lambda sizing, schedule, idle configuration, minimum runtime, and resolved resource tag maps.
- `scale_down.idle_confirmation_seconds`: Number of seconds a runner must consistently report not-busy before scale-down terminates it. GitHub's busy flag can be stale (it can read false for a runner that is actively executing a job), so a single not-busy reading is not sufficient evidence a runner is idle. Set to at least one scale-down schedule interval to require two consecutive not-busy evaluations; a busy reading resets the window. 0 keeps the previous single-reading behaviour.
- `scale_down.tags.resources`: Tags for the scale-down IAM role and EventBridge rule.
- `scale_down.tags.lambda`: Tags for the scale-down Lambda function.
- `scale_down.tags.log_group`: Tags for the scale-down log group.
- `job_retry.enabled`: Enables publishing retry checks from scale-up.
- `job_retry.queue`: Retry queue ARN and URL. Required when job retry is enabled.
- `job_retry.max_attempts`: Maximum queued-job retry attempts.
- `job_retry.delay_in_seconds`: Initial delay before checking the queued job.
- `job_retry.delay_backoff`: Multiplier applied to subsequent delays. | object({
prefix = string
lambda = object({
artifact = object({
zip = string
s3 = object({
bucket = optional(string, null)
key = optional(string, null)
object_version = optional(string, null)
})
})
runtime = string
architecture = string
vpc = object({
subnet_ids = list(string)
security_group_ids = list(string)
})
role = object({
path = string
permissions_boundary = optional(string, null)
principals = optional(list(object({
type = string
identifiers = list(string)
})), [])
})
})
runner = object({
os = string
auto_update_disabled = bool
ephemeral = bool
jit_config_enabled = optional(bool, null)
labels = list(string)
group_name = string
name_prefix = string
boot_time_in_minutes = number
maximum_count = number
})
github = object({
organization_runners = bool
enterprise_server = object({
url = optional(string, null)
ssl_verify = bool
})
user_agent = optional(string, null)
app_parameters = object({
key_base64 = map(string)
id = map(string)
additional_apps_manifest = optional(object({
name = string
arn = string
}), null)
additional_app_parameter_arns = optional(list(string), [])
})
})
queue = object({
build = object({
arn = string
})
kms_key_id = optional(string, null)
event_source_mapping = object({
batch_size = number
maximum_batching_window_in_seconds = number
})
})
ssm = object({
token_path = string
token_path_arn = string
config_path = string
config_path_arn = string
parameter_store_tags = string
kms_key_id = optional(string, null)
})
observability = object({
logs = object({
level = string
retention_in_days = number
kms_key_id = optional(string, null)
class = string
})
tracing = object({
mode = optional(string, null)
capture_http_requests = bool
capture_error = bool
})
metrics = object({
enabled = bool
namespace = string
metric = object({
github_app_rate_limit = object({
enabled = bool
})
})
})
})
scale_up = object({
memory_size = number
timeout = number
reserved_concurrent_executions = number
job_queued_check_enabled = bool
tags = object({
resources = map(string)
lambda = map(string)
log_group = map(string)
event_source_mapping = map(string)
})
})
scale_down = object({
memory_size = number
timeout = number
schedule_expression = string
minimum_running_time_in_minutes = optional(number, null)
idle_confirmation_seconds = optional(number, 0)
idle_config = list(object({
cron = string
timeZone = string
idleCount = number
evictionStrategy = string
}))
tags = object({
resources = map(string)
lambda = map(string)
log_group = map(string)
})
})
job_retry = object({
enabled = bool
max_attempts = number
delay_in_seconds = number
delay_backoff = number
queue = optional(object({
arn = string
url = string
}), null)
})
}) | n/a | yes |
+| [config](#input\_config) | Provider-neutral scale-up and scale-down configuration assembled by runner-config.
- `prefix`: Prefix used to name scaling resources.
- `lambda.artifact.zip`: Resolved local control-plane archive.
- `lambda.artifact.s3.bucket`: Optional S3 bucket containing the Lambda archive.
- `lambda.artifact.s3.key`: Object key of the Lambda archive.
- `lambda.artifact.s3.object_version`: Optional object version of the Lambda archive.
- `lambda.runtime`: Runtime used by both scaling Lambdas.
- `lambda.architecture`: Instruction-set architecture used by both scaling Lambdas.
- `lambda.vpc.subnet_ids`: Subnets used for Lambda VPC configuration.
- `lambda.vpc.security_group_ids`: Security groups used for Lambda VPC configuration.
- `lambda.role.path`: IAM path used for the scaling Lambda roles.
- `lambda.role.permissions_boundary`: Optional permissions boundary for the scaling Lambda roles.
- `lambda.role.principals`: Additional principals allowed to assume the scaling Lambda roles.
- `runner.os`: Runner operating system used for the minimum-runtime default.
- `runner.auto_update_disabled`: Disables the GitHub runner application's built-in updater.
- `runner.ephemeral`: Registers runners in ephemeral mode.
- `runner.jit_config_enabled`: Enables or disables just-in-time runner configuration.
- `runner.labels`: Labels supplied when a runner is registered.
- `runner.group_name`: GitHub runner group used during registration.
- `runner.name_prefix`: Prefix added to registered runner names.
- `runner.boot_time_in_minutes`: Webhook-provider runner boot timeout used by scale-down.
- `runner.maximum_count`: Webhook-provider runner capacity limit for this runner configuration.
- `github.organization_runners`: Registers organization runners when true.
- `github.enterprise_server.url`: Optional GitHub Enterprise Server URL.
- `github.enterprise_server.ssl_verify`: Enables TLS verification for GitHub Enterprise Server.
- `github.user_agent`: Optional User-Agent sent to GitHub.
- `github.app_parameters.key_base64`: Ordered Parameter Store references for GitHub App private keys.
- `github.app_parameters.id`: Ordered Parameter Store references for GitHub App IDs.
- `github.app_parameters.installation_id`: Ordered optional Parameter Store references for GitHub App installation IDs.
- `queue.build.arn`: ARN of the build queue consumed by scale-up.
- `queue.kms_key_id`: Optional KMS key ARN used to encrypt the build queue. This is distinct from the Parameter Store key.
- `queue.event_source_mapping.batch_size`: Maximum records delivered per scale-up invocation.
- `queue.event_source_mapping.maximum_batching_window_in_seconds`: Maximum event batching window.
- `ssm.token_path`: Parameter Store path used for registration tokens.
- `ssm.token_path_arn`: ARN of the Parameter Store path used for registration tokens.
- `ssm.config_path`: Parameter Store path used for persistent runner configuration.
- `ssm.config_path_arn`: ARN of the persistent runner configuration path.
- `ssm.kms_key_id`: Optional KMS key ARN used to decrypt shared parameters. Its value may be unknown until apply.
- `ssm.parameter_store_tags`: JSON-encoded tags applied to parameters created at runtime.
- `observability.logs`: Shared logging level, retention, encryption, and log-class configuration.
- `observability.tracing`: Lambda X-Ray and tracing-helper configuration.
- `observability.metrics`: Metrics enablement, namespace, and GitHub rate-limit metric configuration.
- `scale_up`: Scale-up Lambda sizing, concurrency, queued-job behavior, and resolved resource tag maps.
- `scale_up.tags.resources`: Tags for the scale-up IAM role and other component resources.
- `scale_up.tags.lambda`: Tags for the scale-up Lambda function.
- `scale_up.tags.log_group`: Tags for the scale-up log group.
- `scale_up.tags.event_source_mapping`: Tags for the build-queue event-source mapping.
- `scale_down`: Scale-down Lambda sizing, schedule, idle configuration, minimum runtime, and resolved resource tag maps.
- `scale_down.tags.resources`: Tags for the scale-down IAM role and EventBridge rule.
- `scale_down.tags.lambda`: Tags for the scale-down Lambda function.
- `scale_down.tags.log_group`: Tags for the scale-down log group.
- `job_retry.enabled`: Enables publishing retry checks from scale-up.
- `job_retry.queue`: Retry queue ARN and URL. Required when job retry is enabled.
- `job_retry.max_attempts`: Maximum queued-job retry attempts.
- `job_retry.delay_in_seconds`: Initial delay before checking the queued job.
- `job_retry.delay_backoff`: Multiplier applied to subsequent delays. | object({
prefix = string
lambda = object({
artifact = object({
zip = string
s3 = object({
bucket = optional(string, null)
key = optional(string, null)
object_version = optional(string, null)
})
})
runtime = string
architecture = string
vpc = object({
subnet_ids = list(string)
security_group_ids = list(string)
})
role = object({
path = string
permissions_boundary = optional(string, null)
principals = optional(list(object({
type = string
identifiers = list(string)
})), [])
})
})
runner = object({
os = string
auto_update_disabled = bool
ephemeral = bool
jit_config_enabled = optional(bool, null)
labels = list(string)
group_name = string
name_prefix = string
boot_time_in_minutes = number
maximum_count = number
})
github = object({
organization_runners = bool
enterprise_server = object({
url = optional(string, null)
ssl_verify = bool
})
user_agent = optional(string, null)
app_parameters = object({
key_base64 = list(map(string))
id = list(map(string))
installation_id = list(object({ name = string, arn = string }))
})
})
queue = object({
build = object({
arn = string
})
kms_key_id = optional(string, null)
event_source_mapping = object({
batch_size = number
maximum_batching_window_in_seconds = number
})
})
ssm = object({
token_path = string
token_path_arn = string
config_path = string
config_path_arn = string
parameter_store_tags = string
kms_key_id = optional(string, null)
})
observability = object({
logs = object({
level = string
retention_in_days = number
kms_key_id = optional(string, null)
class = string
})
tracing = object({
mode = optional(string, null)
capture_http_requests = bool
capture_error = bool
})
metrics = object({
enabled = bool
namespace = string
metric = object({
github_app_rate_limit = object({
enabled = bool
})
})
})
})
scale_up = object({
memory_size = number
timeout = number
reserved_concurrent_executions = number
job_queued_check_enabled = bool
tags = object({
resources = map(string)
lambda = map(string)
log_group = map(string)
event_source_mapping = map(string)
})
})
scale_down = object({
memory_size = number
timeout = number
schedule_expression = string
minimum_running_time_in_minutes = optional(number, null)
idle_config = list(object({
cron = string
timeZone = string
idleCount = number
evictionStrategy = string
}))
tags = object({
resources = map(string)
lambda = map(string)
log_group = map(string)
})
})
job_retry = object({
enabled = bool
max_attempts = number
delay_in_seconds = number
delay_backoff = number
queue = optional(object({
arn = string
url = string
}), null)
})
}) | n/a | yes |
| [runner\_provider](#input\_runner\_provider) | Selected compute-provider integration for the scaling control plane.
- `type`: Compute-provider discriminator supplied to both Lambdas.
- `scale_up.environment_variables`: Provider-specific scale-up environment variables.
- `scale_up.iam_policy_json`: Provider-specific IAM policy merged into the common scale-up policy.
- `scale_up.additional_iam_policy_json`: Optional additional provider policy attached separately to the scale-up role.
- `scale_up.managed_policy`: Optional provider-managed policy attachment. Object presence controls attachment creation.
- `scale_up.managed_policy.arn`: ARN of the provider-managed policy. The ARN may remain unknown until apply.
- `scale_down.environment_variables`: Provider-specific scale-down environment variables.
- `scale_down.iam_policy_json`: Provider-specific IAM policy merged into the common scale-down policy. | object({
type = string
scale_up = object({
environment_variables = map(string)
iam_policy_json = string
additional_iam_policy_json = optional(string, null)
managed_policy = optional(object({
arn = string
}), null)
})
scale_down = object({
environment_variables = map(string)
iam_policy_json = string
})
}) | n/a | yes |
## Outputs
diff --git a/modules/orchestration-providers/webhook/scale-runners/scale-down-iam-policies.tf b/modules/orchestration-providers/webhook/scale-runners/scale-down-iam-policies.tf
index 46a79df258..b95cb9e686 100644
--- a/modules/orchestration-providers/webhook/scale-runners/scale-down-iam-policies.tf
+++ b/modules/orchestration-providers/webhook/scale-runners/scale-down-iam-policies.tf
@@ -7,12 +7,9 @@ data "aws_iam_policy_document" "scale_down_common" {
"ssm:GetParameters",
]
resources = concat(
- [
- var.config.github.app_parameters.id.arn,
- var.config.github.app_parameters.key_base64.arn,
- ],
- var.config.github.app_parameters.additional_app_parameter_arns,
- var.config.github.app_parameters.additional_apps_manifest != null ? [var.config.github.app_parameters.additional_apps_manifest.arn] : [],
+ [for p in var.config.github.app_parameters.id : p.arn],
+ [for p in var.config.github.app_parameters.key_base64 : p.arn],
+ [for p in var.config.github.app_parameters.installation_id : p.arn if p != null],
)
}
diff --git a/modules/orchestration-providers/webhook/scale-runners/scale-down.tf b/modules/orchestration-providers/webhook/scale-runners/scale-down.tf
index b00c592929..44e651d78c 100644
--- a/modules/orchestration-providers/webhook/scale-runners/scale-down.tf
+++ b/modules/orchestration-providers/webhook/scale-runners/scale-down.tf
@@ -15,26 +15,25 @@ resource "aws_lambda_function" "scale_down" {
environment {
variables = merge(var.runner_provider.scale_down.environment_variables, {
- ENVIRONMENT = var.config.prefix
- ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.config.observability.metrics.enabled && var.config.observability.metrics.metric.github_app_rate_limit.enabled
- GHES_URL = var.config.github.enterprise_server.url
- USER_AGENT = var.config.github.user_agent
- LOG_LEVEL = upper(var.config.observability.logs.level)
- MINIMUM_RUNNING_TIME_IN_MINUTES = coalesce(var.config.scale_down.minimum_running_time_in_minutes, local.min_runtime_defaults[var.config.runner.os])
- SCALE_DOWN_IDLE_CONFIRMATION_SECONDS = var.config.scale_down.idle_confirmation_seconds
- NODE_TLS_REJECT_UNAUTHORIZED = var.config.github.enterprise_server.url != null && !var.config.github.enterprise_server.ssl_verify ? 0 : 1
- PARAMETER_GITHUB_APP_ID_NAME = var.config.github.app_parameters.id.name
- PARAMETER_GITHUB_APP_KEY_BASE64_NAME = var.config.github.app_parameters.key_base64.name
- PARAMETER_GITHUB_APPS_MANIFEST_NAME = var.config.github.app_parameters.additional_apps_manifest != null ? var.config.github.app_parameters.additional_apps_manifest.name : ""
- POWERTOOLS_LOGGER_LOG_EVENT = var.config.observability.logs.level == "debug" ? "true" : "false"
- SCALE_DOWN_CONFIG = jsonencode(var.config.scale_down.idle_config)
- POWERTOOLS_SERVICE_NAME = "${var.config.prefix}-scale-down"
- POWERTOOLS_METRICS_NAMESPACE = var.config.observability.metrics.namespace
- POWERTOOLS_TRACE_ENABLED = var.config.observability.tracing.mode != null
- POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.config.observability.tracing.capture_http_requests
- POWERTOOLS_TRACER_CAPTURE_ERROR = var.config.observability.tracing.capture_error
- COMPUTE_PROVIDER_TYPE = var.runner_provider.type
- RUNNER_BOOT_TIME_IN_MINUTES = var.config.runner.boot_time_in_minutes
+ ENVIRONMENT = var.config.prefix
+ ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.config.observability.metrics.enabled && var.config.observability.metrics.metric.github_app_rate_limit.enabled
+ GHES_URL = var.config.github.enterprise_server.url
+ USER_AGENT = var.config.github.user_agent
+ LOG_LEVEL = upper(var.config.observability.logs.level)
+ MINIMUM_RUNNING_TIME_IN_MINUTES = coalesce(var.config.scale_down.minimum_running_time_in_minutes, local.min_runtime_defaults[var.config.runner.os])
+ NODE_TLS_REJECT_UNAUTHORIZED = var.config.github.enterprise_server.url != null && !var.config.github.enterprise_server.ssl_verify ? 0 : 1
+ PARAMETER_GITHUB_APP_ID_NAME = join(":", [for p in var.config.github.app_parameters.id : p.name])
+ PARAMETER_GITHUB_APP_KEY_BASE64_NAME = join(":", [for p in var.config.github.app_parameters.key_base64 : p.name])
+ PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME = join(":", [for p in var.config.github.app_parameters.installation_id : p != null ? p.name : ""])
+ POWERTOOLS_LOGGER_LOG_EVENT = var.config.observability.logs.level == "debug" ? "true" : "false"
+ SCALE_DOWN_CONFIG = jsonencode(var.config.scale_down.idle_config)
+ POWERTOOLS_SERVICE_NAME = "${var.config.prefix}-scale-down"
+ POWERTOOLS_METRICS_NAMESPACE = var.config.observability.metrics.namespace
+ POWERTOOLS_TRACE_ENABLED = var.config.observability.tracing.mode != null
+ POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.config.observability.tracing.capture_http_requests
+ POWERTOOLS_TRACER_CAPTURE_ERROR = var.config.observability.tracing.capture_error
+ COMPUTE_PROVIDER_TYPE = var.runner_provider.type
+ RUNNER_BOOT_TIME_IN_MINUTES = var.config.runner.boot_time_in_minutes
})
}
diff --git a/modules/orchestration-providers/webhook/scale-runners/scale-up-iam-policies.tf b/modules/orchestration-providers/webhook/scale-runners/scale-up-iam-policies.tf
index b44d01c756..b3c87b8ad7 100644
--- a/modules/orchestration-providers/webhook/scale-runners/scale-up-iam-policies.tf
+++ b/modules/orchestration-providers/webhook/scale-runners/scale-up-iam-policies.tf
@@ -22,12 +22,9 @@ data "aws_iam_policy_document" "scale_up_common" {
"ssm:GetParameters",
]
resources = concat(
- [
- var.config.github.app_parameters.id.arn,
- var.config.github.app_parameters.key_base64.arn,
- ],
- var.config.github.app_parameters.additional_app_parameter_arns,
- var.config.github.app_parameters.additional_apps_manifest != null ? [var.config.github.app_parameters.additional_apps_manifest.arn] : [],
+ [for p in var.config.github.app_parameters.id : p.arn],
+ [for p in var.config.github.app_parameters.key_base64 : p.arn],
+ [for p in var.config.github.app_parameters.installation_id : p.arn if p != null],
[
var.config.ssm.config_path_arn,
"${var.config.ssm.config_path_arn}/*",
diff --git a/modules/orchestration-providers/webhook/scale-runners/scale-up.tf b/modules/orchestration-providers/webhook/scale-runners/scale-up.tf
index 1f0ddcdc5b..2997aeac21 100644
--- a/modules/orchestration-providers/webhook/scale-runners/scale-up.tf
+++ b/modules/orchestration-providers/webhook/scale-runners/scale-up.tf
@@ -16,36 +16,36 @@ resource "aws_lambda_function" "scale_up" {
environment {
variables = merge(var.runner_provider.scale_up.environment_variables, {
- DISABLE_RUNNER_AUTOUPDATE = var.config.runner.auto_update_disabled
- ENABLE_EPHEMERAL_RUNNERS = var.config.runner.ephemeral
- ENABLE_JIT_CONFIG = var.config.runner.jit_config_enabled
- ENABLE_JOB_QUEUED_CHECK = var.config.scale_up.job_queued_check_enabled
- ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.config.observability.metrics.enabled && var.config.observability.metrics.metric.github_app_rate_limit.enabled
- ENABLE_ORGANIZATION_RUNNERS = var.config.github.organization_runners
- ENVIRONMENT = var.config.prefix
- GHES_URL = var.config.github.enterprise_server.url
- USER_AGENT = var.config.github.user_agent
- LOG_LEVEL = upper(var.config.observability.logs.level)
- MINIMUM_RUNNING_TIME_IN_MINUTES = coalesce(var.config.scale_down.minimum_running_time_in_minutes, local.min_runtime_defaults[var.config.runner.os])
- NODE_TLS_REJECT_UNAUTHORIZED = var.config.github.enterprise_server.url != null && !var.config.github.enterprise_server.ssl_verify ? 0 : 1
- PARAMETER_GITHUB_APP_ID_NAME = var.config.github.app_parameters.id.name
- PARAMETER_GITHUB_APP_KEY_BASE64_NAME = var.config.github.app_parameters.key_base64.name
- PARAMETER_GITHUB_APPS_MANIFEST_NAME = var.config.github.app_parameters.additional_apps_manifest != null ? var.config.github.app_parameters.additional_apps_manifest.name : ""
- POWERTOOLS_LOGGER_LOG_EVENT = var.config.observability.logs.level == "debug" ? "true" : "false"
- POWERTOOLS_METRICS_NAMESPACE = var.config.observability.metrics.namespace
- POWERTOOLS_TRACE_ENABLED = var.config.observability.tracing.mode != null
- POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.config.observability.tracing.capture_http_requests
- POWERTOOLS_TRACER_CAPTURE_ERROR = var.config.observability.tracing.capture_error
- RUNNER_LABELS = lower(join(",", var.config.runner.labels))
- RUNNER_GROUP_NAME = var.config.runner.group_name
- RUNNER_NAME_PREFIX = var.config.runner.name_prefix
- COMPUTE_PROVIDER_TYPE = var.runner_provider.type
- RUNNERS_MAXIMUM_COUNT = var.config.runner.maximum_count
- POWERTOOLS_SERVICE_NAME = "${var.config.prefix}-scale-up"
- SSM_TOKEN_PATH = var.config.ssm.token_path
- SSM_CONFIG_PATH = var.config.ssm.config_path
- SSM_PARAMETER_STORE_TAGS = var.config.ssm.parameter_store_tags
- JOB_RETRY_CONFIG = jsonencode(local.job_retry_config)
+ DISABLE_RUNNER_AUTOUPDATE = var.config.runner.auto_update_disabled
+ ENABLE_EPHEMERAL_RUNNERS = var.config.runner.ephemeral
+ ENABLE_JIT_CONFIG = var.config.runner.jit_config_enabled
+ ENABLE_JOB_QUEUED_CHECK = var.config.scale_up.job_queued_check_enabled
+ ENABLE_METRIC_GITHUB_APP_RATE_LIMIT = var.config.observability.metrics.enabled && var.config.observability.metrics.metric.github_app_rate_limit.enabled
+ ENABLE_ORGANIZATION_RUNNERS = var.config.github.organization_runners
+ ENVIRONMENT = var.config.prefix
+ GHES_URL = var.config.github.enterprise_server.url
+ USER_AGENT = var.config.github.user_agent
+ LOG_LEVEL = upper(var.config.observability.logs.level)
+ MINIMUM_RUNNING_TIME_IN_MINUTES = coalesce(var.config.scale_down.minimum_running_time_in_minutes, local.min_runtime_defaults[var.config.runner.os])
+ NODE_TLS_REJECT_UNAUTHORIZED = var.config.github.enterprise_server.url != null && !var.config.github.enterprise_server.ssl_verify ? 0 : 1
+ PARAMETER_GITHUB_APP_ID_NAME = join(":", [for p in var.config.github.app_parameters.id : p.name])
+ PARAMETER_GITHUB_APP_KEY_BASE64_NAME = join(":", [for p in var.config.github.app_parameters.key_base64 : p.name])
+ PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME = join(":", [for p in var.config.github.app_parameters.installation_id : p != null ? p.name : ""])
+ POWERTOOLS_LOGGER_LOG_EVENT = var.config.observability.logs.level == "debug" ? "true" : "false"
+ POWERTOOLS_METRICS_NAMESPACE = var.config.observability.metrics.namespace
+ POWERTOOLS_TRACE_ENABLED = var.config.observability.tracing.mode != null
+ POWERTOOLS_TRACER_CAPTURE_HTTPS_REQUESTS = var.config.observability.tracing.capture_http_requests
+ POWERTOOLS_TRACER_CAPTURE_ERROR = var.config.observability.tracing.capture_error
+ RUNNER_LABELS = lower(join(",", var.config.runner.labels))
+ RUNNER_GROUP_NAME = var.config.runner.group_name
+ RUNNER_NAME_PREFIX = var.config.runner.name_prefix
+ COMPUTE_PROVIDER_TYPE = var.runner_provider.type
+ RUNNERS_MAXIMUM_COUNT = var.config.runner.maximum_count
+ POWERTOOLS_SERVICE_NAME = "${var.config.prefix}-scale-up"
+ SSM_TOKEN_PATH = var.config.ssm.token_path
+ SSM_CONFIG_PATH = var.config.ssm.config_path
+ SSM_PARAMETER_STORE_TAGS = var.config.ssm.parameter_store_tags
+ JOB_RETRY_CONFIG = jsonencode(local.job_retry_config)
})
}
diff --git a/modules/orchestration-providers/webhook/scale-runners/tests/scale-runners.tftest.hcl b/modules/orchestration-providers/webhook/scale-runners/tests/scale-runners.tftest.hcl
index 9d45a441d0..acca05980b 100644
--- a/modules/orchestration-providers/webhook/scale-runners/tests/scale-runners.tftest.hcl
+++ b/modules/orchestration-providers/webhook/scale-runners/tests/scale-runners.tftest.hcl
@@ -60,22 +60,32 @@ variables {
}
user_agent = "scale-runners-test"
app_parameters = {
- key_base64 = {
- name = "/github-runner/key-base64"
- arn = "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/key-base64"
- }
- id = {
- name = "/github-runner/app-id"
- arn = "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/app-id"
- }
- additional_apps_manifest = {
- name = "/github-runner/additional-apps-manifest"
- arn = "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/additional-apps-manifest"
- }
- additional_app_parameter_arns = [
- "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/app-id-2",
- "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/key-base64-2",
- "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/installation-id-2",
+ key_base64 = [
+ {
+ name = "/github-runner/key-base64"
+ arn = "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/key-base64"
+ },
+ {
+ name = "/github-runner/key-base64-2"
+ arn = "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/key-base64-2"
+ },
+ ]
+ id = [
+ {
+ name = "/github-runner/app-id"
+ arn = "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/app-id"
+ },
+ {
+ name = "/github-runner/app-id-2"
+ arn = "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/app-id-2"
+ },
+ ]
+ installation_id = [
+ null,
+ {
+ name = "/github-runner/installation-id-2"
+ arn = "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/installation-id-2"
+ },
]
}
}
@@ -251,15 +261,14 @@ run "assembles_provider_neutral_scaling_control_plane" {
assert {
condition = (
- aws_lambda_function.scale_up.environment[0].variables["PARAMETER_GITHUB_APP_ID_NAME"] == "/github-runner/app-id"
- && aws_lambda_function.scale_down.environment[0].variables["PARAMETER_GITHUB_APP_KEY_BASE64_NAME"] == "/github-runner/key-base64"
- && aws_lambda_function.scale_up.environment[0].variables["PARAMETER_GITHUB_APPS_MANIFEST_NAME"] == "/github-runner/additional-apps-manifest"
+ aws_lambda_function.scale_up.environment[0].variables["PARAMETER_GITHUB_APP_ID_NAME"] == "/github-runner/app-id:/github-runner/app-id-2"
+ && aws_lambda_function.scale_down.environment[0].variables["PARAMETER_GITHUB_APP_KEY_BASE64_NAME"] == "/github-runner/key-base64:/github-runner/key-base64-2"
+ && aws_lambda_function.scale_up.environment[0].variables["PARAMETER_GITHUB_APP_INSTALLATION_ID_NAME"] == ":/github-runner/installation-id-2"
&& contains(data.aws_iam_policy_document.scale_up_common.statement[1].resources, "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/app-id-2")
&& contains(data.aws_iam_policy_document.scale_down_common.statement[0].resources, "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/key-base64-2")
&& contains(data.aws_iam_policy_document.scale_down_common.statement[0].resources, "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/installation-id-2")
- && contains(data.aws_iam_policy_document.scale_up_common.statement[1].resources, "arn:aws-us-gov:ssm:us-gov-west-1:123456789012:parameter/github-runner/additional-apps-manifest")
)
- error_message = "Scale-up and scale-down must receive the new GitHub App parameter format and grant access to every corresponding SSM ARN."
+ error_message = "Scale-up and scale-down must pass every GitHub App parameter and grant access to every corresponding SSM ARN."
}
assert {
diff --git a/modules/orchestration-providers/webhook/scale-runners/variables.tf b/modules/orchestration-providers/webhook/scale-runners/variables.tf
index 4134b2e1b7..e191e303d1 100644
--- a/modules/orchestration-providers/webhook/scale-runners/variables.tf
+++ b/modules/orchestration-providers/webhook/scale-runners/variables.tf
@@ -33,10 +33,9 @@ variable "config" {
- `github.enterprise_server.url`: Optional GitHub Enterprise Server URL.
- `github.enterprise_server.ssl_verify`: Enables TLS verification for GitHub Enterprise Server.
- `github.user_agent`: Optional User-Agent sent to GitHub.
- - `github.app_parameters.key_base64`: Parameter Store reference for the primary GitHub App private key.
- - `github.app_parameters.id`: Parameter Store reference for the primary GitHub App ID.
- - `github.app_parameters.additional_apps_manifest`: Optional Parameter Store reference containing the additional GitHub App manifest.
- - `github.app_parameters.additional_app_parameter_arns`: ARNs of the additional GitHub App credential parameters.
+ - `github.app_parameters.key_base64`: Ordered Parameter Store references for GitHub App private keys.
+ - `github.app_parameters.id`: Ordered Parameter Store references for GitHub App IDs.
+ - `github.app_parameters.installation_id`: Ordered optional Parameter Store references for GitHub App installation IDs.
- `queue.build.arn`: ARN of the build queue consumed by scale-up.
- `queue.kms_key_id`: Optional KMS key ARN used to encrypt the build queue. This is distinct from the Parameter Store key.
- `queue.event_source_mapping.batch_size`: Maximum records delivered per scale-up invocation.
@@ -56,7 +55,6 @@ variable "config" {
- `scale_up.tags.log_group`: Tags for the scale-up log group.
- `scale_up.tags.event_source_mapping`: Tags for the build-queue event-source mapping.
- `scale_down`: Scale-down Lambda sizing, schedule, idle configuration, minimum runtime, and resolved resource tag maps.
- - `scale_down.idle_confirmation_seconds`: Number of seconds a runner must consistently report not-busy before scale-down terminates it. GitHub's busy flag can be stale (it can read false for a runner that is actively executing a job), so a single not-busy reading is not sufficient evidence a runner is idle. Set to at least one scale-down schedule interval to require two consecutive not-busy evaluations; a busy reading resets the window. 0 keeps the previous single-reading behaviour.
- `scale_down.tags.resources`: Tags for the scale-down IAM role and EventBridge rule.
- `scale_down.tags.lambda`: Tags for the scale-down Lambda function.
- `scale_down.tags.log_group`: Tags for the scale-down log group.
@@ -112,13 +110,9 @@ variable "config" {
})
user_agent = optional(string, null)
app_parameters = object({
- key_base64 = map(string)
- id = map(string)
- additional_apps_manifest = optional(object({
- name = string
- arn = string
- }), null)
- additional_app_parameter_arns = optional(list(string), [])
+ key_base64 = list(map(string))
+ id = list(map(string))
+ installation_id = list(object({ name = string, arn = string }))
})
})
queue = object({
@@ -178,7 +172,6 @@ variable "config" {
timeout = number
schedule_expression = string
minimum_running_time_in_minutes = optional(number, null)
- idle_confirmation_seconds = optional(number, 0)
idle_config = list(object({
cron = string
timeZone = string
diff --git a/modules/orchestration-providers/webhook/scale-runners/versions.tf b/modules/orchestration-providers/webhook/scale-runners/versions.tf
index 0bedc91fd5..3ef011ea0a 100644
--- a/modules/orchestration-providers/webhook/scale-runners/versions.tf
+++ b/modules/orchestration-providers/webhook/scale-runners/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.4.0"
required_providers {
aws = {
diff --git a/modules/orchestration-providers/webhook/tests/webhook.tftest.hcl b/modules/orchestration-providers/webhook/tests/webhook.tftest.hcl
index b4df7b2df6..ac92fb907b 100644
--- a/modules/orchestration-providers/webhook/tests/webhook.tftest.hcl
+++ b/modules/orchestration-providers/webhook/tests/webhook.tftest.hcl
@@ -30,14 +30,15 @@ variables {
github = {
app_parameters = {
- key_base64 = {
+ key_base64 = [{
name = "/github-runner/key-base64"
arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64"
- }
- id = {
+ }]
+ id = [{
name = "/github-runner/app-id"
arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id"
- }
+ }]
+ installation_id = [null]
}
enterprise_server = {
url = null
diff --git a/modules/orchestration-providers/webhook/variables.tf b/modules/orchestration-providers/webhook/variables.tf
index 0e7ecd37c8..5dfecdbd6c 100644
--- a/modules/orchestration-providers/webhook/variables.tf
+++ b/modules/orchestration-providers/webhook/variables.tf
@@ -45,7 +45,6 @@ variable "config" {
- `lambda.scale.down.timeout`: Scale-down Lambda timeout in seconds.
- `lambda.scale.down.schedule_expression`: EventBridge schedule expression that invokes scale-down.
- `lambda.scale.down.minimum_running_time_in_minutes`: Optional minimum runner age before scale-down may terminate it. Null selects the operating-system default.
- - `lambda.scale.down.idle_confirmation_seconds`: Number of seconds a runner must consistently report not-busy before scale-down terminates it. A value of `0` preserves the single-reading behavior.
- `lambda.scale.down.idle_config`: Time-based desired idle-runner configurations.
- `lambda.scale.down.idle_config[].cron`: Cron expression identifying when the idle configuration applies.
- `lambda.scale.down.idle_config[].timeZone`: IANA time zone used to evaluate the cron expression.
@@ -114,7 +113,6 @@ variable "config" {
timeout = number
schedule_expression = string
minimum_running_time_in_minutes = optional(number, null)
- idle_confirmation_seconds = optional(number, 0)
idle_config = list(object({
cron = string
timeZone = string
@@ -169,13 +167,9 @@ variable "github" {
description = "Common GitHub API client and GitHub App Parameter Store references."
type = object({
app_parameters = object({
- key_base64 = map(string)
- id = map(string)
- additional_apps_manifest = optional(object({
- name = string
- arn = string
- }), null)
- additional_app_parameter_arns = optional(list(string), [])
+ key_base64 = list(map(string))
+ id = list(map(string))
+ installation_id = list(object({ name = string, arn = string }))
})
enterprise_server = object({
url = optional(string, null)
diff --git a/modules/orchestration-providers/webhook/versions.tf b/modules/orchestration-providers/webhook/versions.tf
index 0bedc91fd5..3ef011ea0a 100644
--- a/modules/orchestration-providers/webhook/versions.tf
+++ b/modules/orchestration-providers/webhook/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.4.0"
required_providers {
aws = {
diff --git a/modules/runner-binaries-syncer/README.md b/modules/runner-binaries-syncer/README.md
index 3355a5d4ff..41b2bdd1e5 100644
--- a/modules/runner-binaries-syncer/README.md
+++ b/modules/runner-binaries-syncer/README.md
@@ -36,7 +36,7 @@ yarn run dist
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [aws](#requirement\_aws) | >= 6.21 |
## Providers
diff --git a/modules/runner-binaries-syncer/versions.tf b/modules/runner-binaries-syncer/versions.tf
index 1238b79cc3..42a40b33fd 100644
--- a/modules/runner-binaries-syncer/versions.tf
+++ b/modules/runner-binaries-syncer/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
diff --git a/modules/runner-config/README.md b/modules/runner-config/README.md
index 7c1585dca2..eace9fe184 100644
--- a/modules/runner-config/README.md
+++ b/modules/runner-config/README.md
@@ -70,7 +70,7 @@ yarn run dist
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.4.0 |
| [aws](#requirement\_aws) | >= 6.33 |
## Providers
@@ -111,10 +111,10 @@ yarn run dist
| [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.
- `aws`: AWS compute-provider configurations.
- `aws.ec2`: EC2 compute-provider configuration.
- `aws.ec2.ami`: Optional AMI discovery or external AMI-parameter configuration. Null uses the operating-system and architecture defaults.
- `aws.ec2.ami.filter`: EC2 AMI filters combined with the provider's default AMI-name filter.
- `aws.ec2.ami.owners`: AWS account IDs or aliases allowed to own the selected AMI.
- `aws.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.
- `aws.ec2.ami.id_ssm_parameter.arn`: ARN of the externally managed SSM parameter. The ARN may be unknown until apply.
- `aws.ec2.ami.kms_key`: Optional KMS key required to launch encrypted AMIs or snapshots. The wrapper's presence is the plan-time policy discriminator.
- `aws.ec2.ami.kms_key.arn`: ARN of the KMS key. The ARN may be unknown until apply.
- `aws.ec2.vpc_id`: VPC in which runner networking resources are created.
- `aws.ec2.subnet_ids`: Subnets from which scale-up may launch runner instances.
- `aws.ec2.overrides`: Optional resource-name overrides.
- `aws.ec2.overrides.name_runner`: Name tag used for runner compute resources. An empty value uses the generated provider name.
- `aws.ec2.overrides.name_sg`: Name tag used for the managed runner security group. An empty value uses the generated provider name.
- `aws.ec2.instance_profile`: Optional externally managed instance profile used by the launch template.
- `aws.ec2.instance_profile.name`: Name of the externally managed instance profile.
- `aws.ec2.instance_profile_path`: IAM path for the provider-managed instance profile. Null uses a path derived from the runner-configuration prefix.
- `aws.ec2.binaries_syncer`: Runner-distribution synchronization configuration.
- `aws.ec2.binaries_syncer.enabled`: Enables use of a synchronized runner distribution from S3.
- `aws.ec2.binaries_syncer.s3`: S3 object containing the synchronized runner distribution. Required when synchronization is enabled.
- `aws.ec2.binaries_syncer.s3.arn`: ARN of the runner-distribution bucket, used by IAM policies.
- `aws.ec2.binaries_syncer.s3.id`: Bucket name used to construct the runner-distribution S3 URI.
- `aws.ec2.binaries_syncer.s3.key`: Object key of the runner distribution.
- `aws.ec2.block_device_mappings`: EBS mappings added to the runner launch template.
- `aws.ec2.block_device_mappings[].delete_on_termination`: Deletes the volume when its runner instance terminates.
- `aws.ec2.block_device_mappings[].device_name`: Device name exposed to the runner instance.
- `aws.ec2.block_device_mappings[].encrypted`: Enables EBS encryption.
- `aws.ec2.block_device_mappings[].iops`: Provisioned IOPS for volume types that support it.
- `aws.ec2.block_device_mappings[].kms_key_id`: KMS key ID or ARN used to encrypt the volume.
- `aws.ec2.block_device_mappings[].snapshot_id`: Snapshot used to initialize the volume.
- `aws.ec2.block_device_mappings[].throughput`: Provisioned throughput for volume types that support it.
- `aws.ec2.block_device_mappings[].volume_initialization_rate`: Fixed initialization rate in MiB/s for supported snapshot-backed volumes.
- `aws.ec2.block_device_mappings[].volume_size`: Volume size in GiB.
- `aws.ec2.block_device_mappings[].volume_type`: EBS volume type.
- `aws.ec2.ebs_optimized`: Requests EBS-optimized runner instances.
- `aws.ec2.instance_target_capacity_type`: Primary capacity type, either `spot` or `on-demand`.
- `aws.ec2.instance_allocation_strategy`: EC2 Fleet allocation strategy used to select instance capacity.
- `aws.ec2.instance_type_priorities`: Optional numeric priorities keyed by instance type.
- `aws.ec2.instance_max_spot_price`: Optional maximum hourly Spot price.
- `aws.ec2.instance_types`: EC2 instance types available to the scale-up and pool functions.
- `aws.ec2.user_data`: Runner bootstrap user-data configuration.
- `aws.ec2.user_data.enabled`: Enables launch-template user data.
- `aws.ec2.user_data.template`: Optional path to a custom user-data template.
- `aws.ec2.user_data.content`: Optional complete user-data content. When set, it is used instead of rendering a template.
- `aws.ec2.user_data.pre_install`: Script content inserted before runner installation in the default template.
- `aws.ec2.user_data.post_install`: Script content inserted after runner installation in the default template.
- `aws.ec2.user_data.debug_logging_enabled`: Enables verbose user-data tracing, which can expose secrets in logs.
- `aws.ec2.ssm_enabled`: Attaches runner permissions and policies required for AWS Systems Manager access.
- `aws.ec2.create_service_linked_role_spot`: Allows scale-up to create the EC2 Spot service-linked role.
- `aws.ec2.cloudwatch_agent`: CloudWatch agent configuration for runner instances.
- `aws.ec2.cloudwatch_agent.enabled`: Installs and configures the CloudWatch agent through the default bootstrap flow.
- `aws.ec2.cloudwatch_agent.config`: Optional complete CloudWatch agent configuration. Null renders the provider default from `log_files`.
- `aws.ec2.managed_security_group_enabled`: Creates and attaches the provider-managed runner security group.
- `aws.ec2.log_files`: Optional log files collected by the CloudWatch agent. Null uses the provider defaults.
- `aws.ec2.log_files[].log_group_name`: CloudWatch log-group name, before optional prefixing.
- `aws.ec2.log_files[].prefix_log_group`: Prefixes the log-group name with the runner configuration path when true.
- `aws.ec2.log_files[].file_path`: File or glob read by the CloudWatch agent.
- `aws.ec2.log_files[].log_stream_name`: CloudWatch log-stream name template.
- `aws.ec2.log_files[].log_class`: CloudWatch log-group class for the collected file.
- `aws.ec2.key_name`: Optional EC2 key-pair name added to the launch template.
- `aws.ec2.additional_security_group_ids`: Existing security groups attached in addition to the managed security group.
- `aws.ec2.detailed_monitoring_enabled`: Enables detailed EC2 monitoring for runner instances.
- `aws.ec2.egress_rules`: Egress rules created on the managed runner security group.
- `aws.ec2.egress_rules[].cidr_blocks`: IPv4 CIDR destinations.
- `aws.ec2.egress_rules[].ipv6_cidr_blocks`: IPv6 CIDR destinations.
- `aws.ec2.egress_rules[].prefix_list_ids`: AWS prefix-list destinations.
- `aws.ec2.egress_rules[].from_port`: First destination port in the permitted range.
- `aws.ec2.egress_rules[].protocol`: IP protocol name or number. Use `-1` for all protocols.
- `aws.ec2.egress_rules[].security_groups`: Destination security-group IDs.
- `aws.ec2.egress_rules[].self`: Allows traffic to the managed security group itself when true.
- `aws.ec2.egress_rules[].to_port`: Last destination port in the permitted range.
- `aws.ec2.egress_rules[].description`: Optional rule description.
- `aws.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.
- `aws.ec2.metadata_options`: Instance Metadata Service configuration in the launch template.
- `aws.ec2.metadata_options.instance_metadata_tags`: Exposes instance tags through Instance Metadata Service when `enabled`.
- `aws.ec2.metadata_options.http_endpoint`: Enables or disables the Instance Metadata Service endpoint.
- `aws.ec2.metadata_options.http_tokens`: Controls whether IMDSv2 session tokens are optional or required.
- `aws.ec2.metadata_options.http_put_response_hop_limit`: Network hop limit for Instance Metadata Service token responses.
- `aws.ec2.credit_specification`: CPU credit mode for burstable instance types, either `standard` or `unlimited`.
- `aws.ec2.cpu_options`: CPU topology and processor-feature configuration.
- `aws.ec2.cpu_options.core_count`: Number of CPU cores exposed to the runner instance.
- `aws.ec2.cpu_options.threads_per_core`: Number of hardware threads exposed per CPU core.
- `aws.ec2.cpu_options.amd_sev_snp`: Enables or disables AMD SEV-SNP on supported instance types.
- `aws.ec2.cpu_options.nested_virtualization`: Enables or disables nested virtualization on supported instance types.
- `aws.ec2.placement`: EC2 placement configuration for runner instances.
- `aws.ec2.placement.affinity`: Host affinity setting.
- `aws.ec2.placement.availability_zone`: Availability Zone in which the instance is placed.
- `aws.ec2.placement.group_id`: Placement-group ID.
- `aws.ec2.placement.group_name`: Placement-group name.
- `aws.ec2.placement.host_id`: Dedicated Host ID.
- `aws.ec2.placement.host_resource_group_arn`: ARN of the host resource group used for placement.
- `aws.ec2.placement.spread_domain`: Spread-domain placement value.
- `aws.ec2.placement.tenancy`: Instance tenancy, such as `default`, `dedicated`, or `host`.
- `aws.ec2.placement.partition_number`: Placement-group partition number.
- `aws.ec2.license_specifications`: License Manager configurations added to the launch template.
- `aws.ec2.license_specifications[].license_configuration_arn`: ARN of a License Manager license configuration.
- `aws.ec2.associate_public_ipv4_address`: Associates a public IPv4 address with runner network interfaces.
- `aws.ec2.on_demand_failover_for_errors`: EC2 error codes that trigger an on-demand fallback after a Spot launch failure.
- `aws.ec2.scale_errors`: EC2 error codes treated as retryable scale-up failures.
- `aws.ec2.use_dedicated_host`: Enables the dedicated-host launch path, required for macOS runners. | object({
aws = optional(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)
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\_key](#input\_compute\_provider\_key) | Optional plan-known compute-provider dispatch key. Null discovers the key from the exactly one populated compute\_provider block. | `string` | `null` | no |
-| [github](#input\_github) | GitHub API and runner-registration configuration.
- `app_parameters.key_base64`: Parameter Store reference for the primary GitHub App private key.
- `app_parameters.id`: Parameter Store reference for the primary GitHub App ID.
- `app_parameters.additional_apps_manifest`: Optional Parameter Store reference containing the additional GitHub App manifest.
- `app_parameters.additional_app_parameter_arns`: ARNs of the additional GitHub App credential parameters.
- `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)
additional_apps_manifest = optional(object({
name = string
arn = string
}), null)
additional_app_parameter_arns = optional(list(string), [])
})
enterprise_server = optional(object({
url = optional(string, null)
ssl_verify = optional(bool, true)
}), {})
user_agent = optional(string, null)
}) | n/a | yes |
+| [github](#input\_github) | GitHub API and runner-registration configuration.
- `app_parameters.key_base64`: Ordered Parameter Store references for GitHub App private keys.
- `app_parameters.id`: Ordered Parameter Store references for GitHub App IDs.
- `app_parameters.installation_id`: Ordered optional Parameter Store references for GitHub App installation IDs.
- `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 = list(map(string))
id = list(map(string))
installation_id = list(object({ name = string, arn = string }))
})
enterprise_server = optional(object({
url = optional(string, null)
ssl_verify = optional(bool, true)
}), {})
user_agent = optional(string, null)
}) | n/a | yes |
| [lambda](#input\_lambda) | Common Lambda substrate independent of the selected runner orchestration provider.
- `artifact.s3.bucket`: Optional shared S3 bucket containing component-owned Lambda artifacts. An orchestration provider selects its own object key and version; the bucket alone selects no artifact.
- `runtime`: Runtime used by the control-plane Lambda functions.
- `architecture`: Instruction-set architecture used by the 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.
- `principals`: Additional principals allowed to assume the control-plane Lambda roles.
- `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({
artifact = optional(object({
s3 = optional(object({
bucket = 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), {})
principals = optional(list(object({
type = string
identifiers = list(string)
})), [])
role = optional(object({
path = optional(string, null)
permissions_boundary = optional(string, null)
}), {})
}) | `{}` | no |
| [observability](#input\_observability) | Logging, tracing, and metrics configuration for control-plane and provider resources.
- `logs.level`: Application log level supplied to the control-plane functions.
- `logs.retention_in_days`: CloudWatch Logs retention period.
- `logs.kms_key_id`: Optional KMS key ID or ARN used to encrypt CloudWatch log groups.
- `logs.class`: CloudWatch log-group class. Supported values are `STANDARD` and `INFREQUENT_ACCESS`.
- `logs.tags`: Shared tags for CloudWatch log groups. These override module-level `tags`; component `tags` override this map when keys conflict.
- `tracing.mode`: Optional Lambda active-tracing mode. Null disables X-Ray tracing configuration.
- `tracing.capture_http_requests`: Enables HTTP request capture in the tracing helper.
- `tracing.capture_error`: Enables error capture in the tracing helper.
- `metrics.enabled`: Enables module-emitted metrics.
- `metrics.namespace`: CloudWatch namespace used for emitted metrics.
- `metrics.metric.github_app_rate_limit.enabled`: Emits GitHub App rate-limit metrics.
- `metrics.metric.job_retry.enabled`: Emits job-retry metrics.
- `metrics.metric.spot_termination_warning.enabled`: Emits spot-termination warning metrics where supported. | object({
logs = optional(object({
level = optional(string, "info")
retention_in_days = optional(number, 180)
kms_key_id = optional(string, null)
class = optional(string, "STANDARD")
tags = optional(map(string), {})
}), {})
tracing = optional(object({
mode = optional(string, null)
capture_http_requests = optional(bool, false)
capture_error = optional(bool, false)
}), {})
metrics = optional(object({
enabled = optional(bool, false)
namespace = optional(string, "GitHub Runners")
metric = optional(object({
github_app_rate_limit = optional(object({
enabled = optional(bool, true)
}), {})
job_retry = optional(object({
enabled = optional(bool, true)
}), {})
spot_termination_warning = optional(object({
enabled = optional(bool, true)
}), {})
}), {})
}), {})
}) | `{}` | no |
-| [orchestration\_provider](#input\_orchestration\_provider) | Runner demand-orchestration provider configuration. Exactly one provider block must be non-null. Wrapper presence selects the provider and must therefore be known during planning; values inside the selected provider may remain unknown until apply.
- `webhook`: Selects the workflow-job webhook control plane. It owns runner lifecycle and capacity, the build queue reference, the runner-control artifact, scale-up, scale-down, scheduled pool, and optional job-retry controls. Future providers can be added as sibling blocks without moving this contract.
- `webhook.runner`: Runner lifecycle, boot timeout, and capacity settings owned by webhook orchestration.
- `webhook.runner.boot_time_in_minutes`: Expected runner boot duration used by scale-down and pool controls. The default is `5`.
- `webhook.runner.ephemeral`: Registers runners in ephemeral mode. The default is `false`.
- `webhook.runner.jit_config_enabled`: Explicitly enables or disables just-in-time configuration. The default is null, which follows `runner.ephemeral`.
- `webhook.runner.maximum_count`: Maximum number of runners managed for this runner configuration. The default is `3`.
- `webhook.github.organization_runners`: Registers runners at organization scope when true; otherwise registration is repository-scoped.
- `webhook.queue.build.arn`: ARN of the runner configuration's build queue.
- `webhook.queue.build.url`: URL of the runner configuration's build queue.
- `webhook.queue.kms_key_id`: Optional KMS key ARN encrypting the build queue. The default is null and is independent from the Parameter Store KMS key.
- `webhook.queue.tags`: Tags inherited by queue-related provider resources before component-specific overrides. The default is `{}`.
- `webhook.lambda.artifact`: Runner-control artifact shared by scale, pool, and job-retry components. Set at most one of `zip` or `s3`; no selection uses the packaged runner archive.
- `webhook.lambda.artifact.zip`: Optional local path to the runner-control Lambda archive. The default is null.
- `webhook.lambda.artifact.s3`: Optional S3 object selector in the common `lambda.artifact.s3.bucket`. Wrapper presence must be known during planning, selecting it requires a non-null common bucket, and the default is null.
- `webhook.lambda.artifact.s3.key`: Object key of the runner-control Lambda archive.
- `webhook.lambda.artifact.s3.object_version`: Optional object version of the runner-control Lambda archive. The default is null.
- `webhook.lambda.scale.up.memory_size`: Memory allocated to the scale-up Lambda in MB. The default is `512`.
- `webhook.lambda.scale.up.timeout`: Scale-up Lambda timeout in seconds. The default is `60`.
- `webhook.lambda.scale.up.reserved_concurrent_executions`: Reserved concurrency for scale-up. The default is `1`; use `-1` for unreserved concurrency.
- `webhook.lambda.scale.up.job_queued_check_enabled`: Enables queued-job verification before scaling. The default is null, which follows the resolved runner mode.
- `webhook.lambda.scale.up.event_source_mapping.batch_size`: Maximum build-queue records delivered per scale-up invocation. The default is `10`.
- `webhook.lambda.scale.up.event_source_mapping.maximum_batching_window_in_seconds`: Maximum batching window for build-queue records. The default is `0`.
- `webhook.lambda.scale.up.tags`: Tags applied within scale-up resource scopes after common provider tags. The default is `{}`.
- `webhook.lambda.scale.down.memory_size`: Memory allocated to the scale-down Lambda in MB. The default is `512`.
- `webhook.lambda.scale.down.timeout`: Scale-down Lambda timeout in seconds. The default is `60`.
- `webhook.lambda.scale.down.schedule_expression`: EventBridge schedule expression that invokes scale-down. The default is `cron(*/5 * * * ? *)`.
- `webhook.lambda.scale.down.minimum_running_time_in_minutes`: Optional minimum runner age before scale-down may terminate it. The default is null, which selects the operating-system default.
- `webhook.lambda.scale.down.idle_confirmation_seconds`: Number of seconds a runner must consistently report not-busy before scale-down terminates it. The default is `0`, which preserves the single-reading behavior.
- `webhook.lambda.scale.down.idle_config`: Time-based desired idle-runner configurations. The default is `[]`.
- `webhook.lambda.scale.down.idle_config[].cron`: Cron expression identifying when the idle configuration applies.
- `webhook.lambda.scale.down.idle_config[].timeZone`: IANA time zone used to evaluate the cron expression.
- `webhook.lambda.scale.down.idle_config[].idleCount`: Number of idle runners retained during the matching period.
- `webhook.lambda.scale.down.idle_config[].evictionStrategy`: Selection strategy used when excess idle runners are removed. The default is `oldest_first`.
- `webhook.lambda.scale.down.tags`: Tags applied within scale-down resource scopes after common provider tags. The default is `{}`.
- `webhook.lambda.pool.memory_size`: Memory allocated to the pool Lambda in MB. The default is `512`.
- `webhook.lambda.pool.timeout`: Pool Lambda timeout in seconds. The default is `60`.
- `webhook.lambda.pool.reserved_concurrent_executions`: Reserved concurrency for the pool Lambda. The default is `1`; use `-1` for unreserved concurrency.
- `webhook.lambda.pool.config`: Scheduled target pool sizes. The default is `[]`, which disables the pool component.
- `webhook.lambda.pool.config[].schedule_expression`: Scheduler expression that activates the target size.
- `webhook.lambda.pool.config[].schedule_expression_timezone`: Optional IANA time zone used to evaluate the schedule.
- `webhook.lambda.pool.config[].size`: Desired number of runners for the schedule.
- `webhook.lambda.pool.include_busy_runners`: Includes busy runners when reconciling scheduled pool capacity. The default is `false`.
- `webhook.lambda.pool.runner_owner`: Optional GitHub organization or repository owner used for pooled runners. The default is null.
- `webhook.lambda.pool.tags`: Tags applied within pool resource scopes after common provider tags. The default is `{}`.
- `webhook.job_retry.enabled`: Creates the retry queue, Lambda function, event-source mapping, and related IAM resources. The default is `false`.
- `webhook.job_retry.delay_in_seconds`: Initial delay before a queued-job retry check. The default is `300`.
- `webhook.job_retry.delay_backoff`: Multiplier applied to the delay after each unsuccessful check. The default is `2`.
- `webhook.job_retry.max_attempts`: Maximum retry-check attempts before the message is no longer republished. The default is `1`.
- `webhook.job_retry.tags`: Tags applied within job-retry resource scopes after common provider tags. The default is `{}`.
- `webhook.job_retry.lambda.memory_size`: Memory allocated to the job-retry Lambda in MB. The default is `256`.
- `webhook.job_retry.lambda.reserved_concurrent_executions`: Reserved concurrency for job retry. The default is `1`; use `-1` for unreserved concurrency.
- `webhook.job_retry.lambda.timeout`: Job-retry Lambda timeout in seconds and visibility timeout for its retry queue. The default is `30`. | object({
webhook = optional(object({
runner = optional(object({
boot_time_in_minutes = optional(number, 5)
ephemeral = optional(bool, false)
jit_config_enabled = optional(bool, null)
maximum_count = optional(number, 3)
}), {})
github = object({
organization_runners = bool
})
queue = object({
build = object({
arn = string
url = string
})
kms_key_id = optional(string, null)
tags = optional(map(string), {})
})
lambda = optional(object({
artifact = optional(object({
zip = optional(string, null)
s3 = optional(object({
key = string
object_version = optional(string, null)
}), null)
}), {})
scale = optional(object({
up = optional(object({
memory_size = optional(number, 512)
timeout = optional(number, 60)
reserved_concurrent_executions = optional(number, 1)
job_queued_check_enabled = optional(bool, null)
event_source_mapping = optional(object({
batch_size = optional(number, 10)
maximum_batching_window_in_seconds = optional(number, 0)
}), {})
tags = optional(map(string), {})
}), {})
down = optional(object({
memory_size = optional(number, 512)
timeout = optional(number, 60)
schedule_expression = optional(string, "cron(*/5 * * * ? *)")
minimum_running_time_in_minutes = optional(number, null)
idle_confirmation_seconds = optional(number, 0)
idle_config = optional(list(object({
cron = string
timeZone = string
idleCount = number
evictionStrategy = optional(string, "oldest_first")
})), [])
tags = optional(map(string), {})
}), {})
}), {})
pool = optional(object({
memory_size = optional(number, 512)
timeout = optional(number, 60)
reserved_concurrent_executions = optional(number, 1)
config = optional(list(object({
schedule_expression = string
schedule_expression_timezone = optional(string)
size = number
})), [])
include_busy_runners = optional(bool, false)
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)
}), {})
}), {})
}), null)
}) | n/a | yes |
+| [orchestration\_provider](#input\_orchestration\_provider) | Runner demand-orchestration provider configuration. Exactly one provider block must be non-null. Wrapper presence selects the provider and must therefore be known during planning; values inside the selected provider may remain unknown until apply.
- `webhook`: Selects the workflow-job webhook control plane. It owns runner lifecycle and capacity, the build queue reference, the runner-control artifact, scale-up, scale-down, scheduled pool, and optional job-retry controls.
- `scale_set`: Selects scale-set orchestration for this runner config. The multi-runner topology owns the shared controller service and passes this plan-known selection marker to runner-config. Scale-set runners always use ephemeral JIT registration.
- `webhook.runner`: Runner lifecycle, boot timeout, and capacity settings owned by webhook orchestration.
- `webhook.runner.boot_time_in_minutes`: Expected runner boot duration used by scale-down and pool controls. The default is `5`.
- `webhook.runner.ephemeral`: Registers runners in ephemeral mode. The default is `false`.
- `webhook.runner.jit_config_enabled`: Explicitly enables or disables just-in-time configuration. The default is null, which follows `runner.ephemeral`.
- `webhook.runner.maximum_count`: Maximum number of runners managed for this runner configuration. The default is `3`.
- `webhook.github.organization_runners`: Registers runners at organization scope when true; otherwise registration is repository-scoped.
- `webhook.queue.build.arn`: ARN of the runner configuration's build queue.
- `webhook.queue.build.url`: URL of the runner configuration's build queue.
- `webhook.queue.kms_key_id`: Optional KMS key ARN encrypting the build queue. The default is null and is independent from the Parameter Store KMS key.
- `webhook.queue.tags`: Tags inherited by queue-related provider resources before component-specific overrides. The default is `{}`.
- `webhook.lambda.artifact`: Runner-control artifact shared by scale, pool, and job-retry components. Set at most one of `zip` or `s3`; no selection uses the packaged runner archive.
- `webhook.lambda.artifact.zip`: Optional local path to the runner-control Lambda archive. The default is null.
- `webhook.lambda.artifact.s3`: Optional S3 object selector in the common `lambda.artifact.s3.bucket`. Wrapper presence must be known during planning, selecting it requires a non-null common bucket, and the default is null.
- `webhook.lambda.artifact.s3.key`: Object key of the runner-control Lambda archive.
- `webhook.lambda.artifact.s3.object_version`: Optional object version of the runner-control Lambda archive. The default is null.
- `webhook.lambda.scale.up.memory_size`: Memory allocated to the scale-up Lambda in MB. The default is `512`.
- `webhook.lambda.scale.up.timeout`: Scale-up Lambda timeout in seconds. The default is `60`.
- `webhook.lambda.scale.up.reserved_concurrent_executions`: Reserved concurrency for scale-up. The default is `1`; use `-1` for unreserved concurrency.
- `webhook.lambda.scale.up.job_queued_check_enabled`: Enables queued-job verification before scaling. The default is null, which follows the resolved runner mode.
- `webhook.lambda.scale.up.event_source_mapping.batch_size`: Maximum build-queue records delivered per scale-up invocation. The default is `10`.
- `webhook.lambda.scale.up.event_source_mapping.maximum_batching_window_in_seconds`: Maximum batching window for build-queue records. The default is `0`.
- `webhook.lambda.scale.up.tags`: Tags applied within scale-up resource scopes after common provider tags. The default is `{}`.
- `webhook.lambda.scale.down.memory_size`: Memory allocated to the scale-down Lambda in MB. The default is `512`.
- `webhook.lambda.scale.down.timeout`: Scale-down Lambda timeout in seconds. The default is `60`.
- `webhook.lambda.scale.down.schedule_expression`: EventBridge schedule expression that invokes scale-down. The default is `cron(*/5 * * * ? *)`.
- `webhook.lambda.scale.down.minimum_running_time_in_minutes`: Optional minimum runner age before scale-down may terminate it. The default is null, which selects the operating-system default.
- `webhook.lambda.scale.down.idle_config`: Time-based desired idle-runner configurations. The default is `[]`.
- `webhook.lambda.scale.down.idle_config[].cron`: Cron expression identifying when the idle configuration applies.
- `webhook.lambda.scale.down.idle_config[].timeZone`: IANA time zone used to evaluate the cron expression.
- `webhook.lambda.scale.down.idle_config[].idleCount`: Number of idle runners retained during the matching period.
- `webhook.lambda.scale.down.idle_config[].evictionStrategy`: Selection strategy used when excess idle runners are removed. The default is `oldest_first`.
- `webhook.lambda.scale.down.tags`: Tags applied within scale-down resource scopes after common provider tags. The default is `{}`.
- `webhook.lambda.pool.memory_size`: Memory allocated to the pool Lambda in MB. The default is `512`.
- `webhook.lambda.pool.timeout`: Pool Lambda timeout in seconds. The default is `60`.
- `webhook.lambda.pool.reserved_concurrent_executions`: Reserved concurrency for the pool Lambda. The default is `1`; use `-1` for unreserved concurrency.
- `webhook.lambda.pool.config`: Scheduled target pool sizes. The default is `[]`, which disables the pool component.
- `webhook.lambda.pool.config[].schedule_expression`: Scheduler expression that activates the target size.
- `webhook.lambda.pool.config[].schedule_expression_timezone`: Optional IANA time zone used to evaluate the schedule.
- `webhook.lambda.pool.config[].size`: Desired number of runners for the schedule.
- `webhook.lambda.pool.include_busy_runners`: Includes busy runners when reconciling scheduled pool capacity. The default is `false`.
- `webhook.lambda.pool.runner_owner`: Optional GitHub organization or repository owner used for pooled runners. The default is null.
- `webhook.lambda.pool.tags`: Tags applied within pool resource scopes after common provider tags. The default is `{}`.
- `webhook.job_retry.enabled`: Creates the retry queue, Lambda function, event-source mapping, and related IAM resources. The default is `false`.
- `webhook.job_retry.delay_in_seconds`: Initial delay before a queued-job retry check. The default is `300`.
- `webhook.job_retry.delay_backoff`: Multiplier applied to the delay after each unsuccessful check. The default is `2`.
- `webhook.job_retry.max_attempts`: Maximum retry-check attempts before the message is no longer republished. The default is `1`.
- `webhook.job_retry.tags`: Tags applied within job-retry resource scopes after common provider tags. The default is `{}`.
- `webhook.job_retry.lambda.memory_size`: Memory allocated to the job-retry Lambda in MB. The default is `256`.
- `webhook.job_retry.lambda.reserved_concurrent_executions`: Reserved concurrency for job retry. The default is `1`; use `-1` for unreserved concurrency.
- `webhook.job_retry.lambda.timeout`: Job-retry Lambda timeout in seconds and visibility timeout for its retry queue. The default is `30`. | object({
webhook = optional(object({
runner = optional(object({
boot_time_in_minutes = optional(number, 5)
ephemeral = optional(bool, false)
jit_config_enabled = optional(bool, null)
maximum_count = optional(number, 3)
}), {})
github = object({
organization_runners = bool
})
queue = object({
build = object({
arn = string
url = string
})
kms_key_id = optional(string, null)
tags = optional(map(string), {})
})
lambda = optional(object({
artifact = optional(object({
zip = optional(string, null)
s3 = optional(object({
key = string
object_version = optional(string, null)
}), null)
}), {})
scale = optional(object({
up = optional(object({
memory_size = optional(number, 512)
timeout = optional(number, 60)
reserved_concurrent_executions = optional(number, 1)
job_queued_check_enabled = optional(bool, null)
event_source_mapping = optional(object({
batch_size = optional(number, 10)
maximum_batching_window_in_seconds = optional(number, 0)
}), {})
tags = optional(map(string), {})
}), {})
down = optional(object({
memory_size = optional(number, 512)
timeout = optional(number, 60)
schedule_expression = optional(string, "cron(*/5 * * * ? *)")
minimum_running_time_in_minutes = optional(number, null)
idle_config = optional(list(object({
cron = string
timeZone = string
idleCount = number
evictionStrategy = optional(string, "oldest_first")
})), [])
tags = optional(map(string), {})
}), {})
}), {})
pool = optional(object({
memory_size = optional(number, 512)
timeout = optional(number, 60)
reserved_concurrent_executions = optional(number, 1)
config = optional(list(object({
schedule_expression = string
schedule_expression_timezone = optional(string)
size = number
})), [])
include_busy_runners = optional(bool, false)
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)
}), {})
}), {})
}), null)
scale_set = optional(object({
name = string
runner = optional(object({
min_runners = optional(number, 0)
max_runners = optional(number, 10)
boot_time_in_minutes = optional(number, 10)
}), {})
}), null)
}) | n/a | yes |
| [prefix](#input\_prefix) | The prefix used for naming resources. | `string` | `"github-actions"` | no |
| [runner](#input\_runner) | Provider-neutral GitHub runner configuration.
- `os`: Runner operating system. Supported values are `linux`, `osx`, and `windows`.
- `architecture`: Runner distribution architecture, such as `x64` or `arm64`.
- `disable_default_labels`: Prevents GitHub's default self-hosted, operating-system, and architecture labels from being registered.
- `labels`: Complete set of labels supplied to the control-plane functions.
- `group_name`: GitHub runner group used during registration.
- `name_prefix`: Prefix added to registered runner names.
- `run_as_root`: Runs the runner service as root when supported by the compute provider.
- `run_as`: Operating-system user used when `run_as_root` is false.
- `auto_update_disabled`: Disables the GitHub runner application's built-in updater.
- `tags`: Additional tags for common runner resources, currently the managed runner IAM role. These override module-level `tags` with the same key.
- `hooks.job_started`: Script content installed as the runner job-started hook.
- `hooks.job_completed`: Script content installed as the runner job-completed hook.
- `iam.role.arn`: ARN of an externally managed runner role. When set, this module does not create or modify that role.
- `iam.managed_policy_arns`: Named managed-policy ARNs attached to the module-managed runner role.
- `iam.additional_trust_policy_json`: Optional IAM policy document merged with the selected compute provider's default runner-role trust policy.
- `iam.path`: IAM path for the module-managed runner role. Defaults to a path derived from `prefix`.
- `iam.permissions_boundary`: Permissions-boundary ARN for the module-managed runner role. | object({
os = optional(string, "linux")
architecture = optional(string, "x64")
disable_default_labels = optional(bool, false)
labels = list(string)
group_name = optional(string, "Default")
name_prefix = optional(string, "")
run_as_root = optional(bool, false)
run_as = optional(string, "ec2-user")
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)
}), {})
}) | n/a | yes |
| [ssm](#input\_ssm) | Parameter Store paths, encryption, tag scopes, and housekeeper configuration.
- `paths.root`: Root Parameter Store path for this runner configuration.
- `paths.tokens`: Path segment under `paths.root` used for registration tokens and just-in-time configuration.
- `paths.config`: Path segment under `paths.root` used for persistent runner configuration.
- `kms_key_id`: Optional customer-managed KMS key ARN used by control-plane IAM policies to decrypt shared GitHub App parameters. The ARN may be unknown until apply; null omits the provider-owned KMS statements. It does not select encryption for runtime-created runner parameters.
- `tags`: Shared tags for SSM-related resources. These override module-level `tags` and are inherited by parameter and housekeeper resources.
- `parameters.tags`: Tags for Terraform-managed runner configuration parameters and temporary parameters created by the scale-up and pool Lambdas. These override module-level and `ssm.tags` values with the same key.
- `housekeeper.schedule_expression`: EventBridge schedule expression that invokes the SSM housekeeper.
- `housekeeper.state`: EventBridge rule state, such as `ENABLED` or `DISABLED`.
- `housekeeper.tags`: Tags for housekeeper resources, including the Lambda function, log group, EventBridge rule, and IAM role. These override module-level, `ssm.tags`, shared Lambda, and shared log tags when keys conflict.
- `housekeeper.lambda.artifact`: Component-owned SSM-housekeeper artifact selection. Set at most one of `zip` or `s3`; when neither is selected, the module uses its packaged runner control-plane archive. This selector does not inherit an orchestration-provider artifact.
- `housekeeper.lambda.artifact.zip`: Optional local path to the SSM-housekeeper Lambda archive.
- `housekeeper.lambda.artifact.s3`: Optional object key and version in the shared `lambda.artifact.s3.bucket`. Selecting S3 requires that common bucket.
- `housekeeper.lambda.artifact.s3.key`: Object key of the SSM-housekeeper Lambda archive.
- `housekeeper.lambda.artifact.s3.object_version`: Optional object version of the SSM-housekeeper Lambda archive.
- `housekeeper.lambda.memory_size`: Memory allocated to the SSM housekeeper Lambda in MB.
- `housekeeper.lambda.timeout`: SSM housekeeper Lambda timeout in seconds.
- `housekeeper.config.tokenPath`: Parameter Store token path cleaned by the housekeeper. When omitted, the configured runner token path is used.
- `housekeeper.config.minimumDaysOld`: Minimum parameter age in days before deletion is allowed.
- `housekeeper.config.dryRun`: Reports eligible parameters without deleting them when true. | object({
paths = object({
root = string
tokens = string
config = string
})
kms_key_id = optional(string, null)
tags = optional(map(string), {})
parameters = optional(object({
tags = optional(map(string), {})
}), {})
housekeeper = optional(object({
schedule_expression = optional(string, "rate(1 day)")
state = optional(string, "ENABLED")
tags = optional(map(string), {})
lambda = optional(object({
artifact = optional(object({
zip = optional(string, null)
s3 = optional(object({
key = string
object_version = optional(string, null)
}), null)
}), {})
memory_size = optional(number, 512)
timeout = optional(number, 60)
}), {})
config = optional(object({
tokenPath = optional(string)
minimumDaysOld = optional(number, 1)
dryRun = optional(bool, false)
}), {})
}), {})
}) | n/a | yes |
@@ -124,6 +124,7 @@ yarn run dist
| Name | Description |
|------|-------------|
+| [compute\_provider\_contract](#output\_compute\_provider\_contract) | Provider-neutral compute-provider capabilities consumed by topology-level orchestration. |
| [orchestration\_provider](#output\_orchestration\_provider) | Resources grouped under the selected runner orchestration provider. |
| [pool](#output\_pool) | Scheduled pool resources. Null when no pool configuration is supplied. |
| [provider](#output\_provider) | Provider-specific resources grouped under the selected provider namespace and type. |
diff --git a/modules/runner-config/common-config.tf b/modules/runner-config/common-config.tf
index fe12790d9a..660fcc60ab 100644
--- a/modules/runner-config/common-config.tf
+++ b/modules/runner-config/common-config.tf
@@ -1,15 +1,6 @@
# Shared control-plane configuration: naming, paths, tags, and normalized values.
locals {
- common_tags = merge(
- {
- "Name" = format("%s-action-runner", var.prefix)
- },
- {
- "ghr:ssm_config_path" = "${var.ssm.paths.root}/${var.ssm.paths.config}"
- },
- var.tags,
- )
-
+ common_tags = var.tags
runner_tags = merge(local.common_tags, var.runner.tags)
lambda_tags = merge(local.common_tags, var.lambda.tags)
observability_log_tags = merge(local.common_tags, var.observability.logs.tags)
diff --git a/modules/runner-config/compute-provider.aws.ec2.tf b/modules/runner-config/compute-provider.aws.ec2.tf
index e289b70217..5d053a73dc 100644
--- a/modules/runner-config/compute-provider.aws.ec2.tf
+++ b/modules/runner-config/compute-provider.aws.ec2.tf
@@ -12,7 +12,7 @@ module "compute_aws_ec2" {
aws_partition = var.aws_partition
aws_region = var.aws_region
prefix = var.prefix
- tags = local.common_tags
+ tags = var.tags
config = var.compute_provider.aws.ec2
runner = merge(var.runner, {
diff --git a/modules/runner-config/orchestration-provider.tf b/modules/runner-config/orchestration-provider.tf
index 16238fa6c6..4d6c4c4444 100644
--- a/modules/runner-config/orchestration-provider.tf
+++ b/modules/runner-config/orchestration-provider.tf
@@ -7,11 +7,16 @@ locals {
orchestration_provider_type = one(keys(local.orchestration_providers))
orchestration_provider_enabled = {
- webhook = local.orchestration_provider_type == "webhook"
+ webhook = local.orchestration_provider_type == "webhook"
+ scale_set = local.orchestration_provider_type == "scale_set"
}
orchestration_provider_runner_lifecycle = {
webhook = one(module.orchestration_webhook[*].runner_lifecycle)
+ scale_set = {
+ ephemeral = true
+ jit_config_enabled = true
+ }
}[local.orchestration_provider_type]
}
@@ -21,7 +26,7 @@ module "orchestration_webhook" {
aws_partition = var.aws_partition
prefix = var.prefix
- tags = local.common_tags
+ tags = var.tags
config = var.orchestration_provider.webhook
runner = var.runner
@@ -32,7 +37,7 @@ module "orchestration_webhook" {
architecture = var.lambda.architecture
subnet_ids = var.lambda.subnet_ids
security_group_ids = var.lambda.security_group_ids
- tags = local.lambda_tags
+ tags = var.lambda.tags
role = {
path = local.lambda_role_path
permissions_boundary = var.lambda.role.permissions_boundary
diff --git a/modules/runner-config/outputs.tf b/modules/runner-config/outputs.tf
index 486e3261eb..d52230ba5f 100644
--- a/modules/runner-config/outputs.tf
+++ b/modules/runner-config/outputs.tf
@@ -22,13 +22,26 @@ output "pool" {
output "orchestration_provider" {
description = "Resources grouped under the selected runner orchestration provider."
+ value = merge(
+ {
+ webhook = local.orchestration_provider_enabled.webhook ? {
+ scale_up = one(module.orchestration_webhook[*].scale_up)
+ scale_down = one(module.orchestration_webhook[*].scale_down)
+ pool = one(module.orchestration_webhook[*].pool)
+ job_retry = one(module.orchestration_webhook[*].job_retry)
+ } : null
+ },
+ local.orchestration_provider_enabled.scale_set ? {
+ scale_set = {}
+ } : {},
+ )
+}
+
+output "compute_provider_contract" {
+ description = "Provider-neutral compute-provider capabilities consumed by topology-level orchestration."
value = {
- webhook = local.orchestration_provider_enabled.webhook ? {
- scale_up = one(module.orchestration_webhook[*].scale_up)
- scale_down = one(module.orchestration_webhook[*].scale_down)
- pool = one(module.orchestration_webhook[*].pool)
- job_retry = one(module.orchestration_webhook[*].job_retry)
- } : null
+ type = local.provider_contract.type
+ capabilities = local.provider_contract.capabilities
}
}
diff --git a/modules/runner-config/ssm-housekeeper/README.md b/modules/runner-config/ssm-housekeeper/README.md
index b8899e3f43..5f5d1ad166 100644
--- a/modules/runner-config/ssm-housekeeper/README.md
+++ b/modules/runner-config/ssm-housekeeper/README.md
@@ -11,7 +11,7 @@ The module is an implementation detail of the experimental runner configuration.
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [aws](#requirement\_aws) | >= 6.33 |
## Providers
diff --git a/modules/runner-config/ssm-housekeeper/versions.tf b/modules/runner-config/ssm-housekeeper/versions.tf
index 0bedc91fd5..da9769f550 100644
--- a/modules/runner-config/ssm-housekeeper/versions.tf
+++ b/modules/runner-config/ssm-housekeeper/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
diff --git a/modules/runner-config/tests/fixtures/computed-iam-inputs/README.md b/modules/runner-config/tests/fixtures/computed-iam-inputs/README.md
index cf16de0bb5..3bbf0f9027 100644
--- a/modules/runner-config/tests/fixtures/computed-iam-inputs/README.md
+++ b/modules/runner-config/tests/fixtures/computed-iam-inputs/README.md
@@ -3,7 +3,7 @@
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3 |
| [random](#requirement\_random) | ~> 3.0 |
## Providers
diff --git a/modules/runner-config/tests/fixtures/computed-iam-inputs/computed-iam-inputs.tf b/modules/runner-config/tests/fixtures/computed-iam-inputs/computed-iam-inputs.tf
index b442e56166..e765015997 100644
--- a/modules/runner-config/tests/fixtures/computed-iam-inputs/computed-iam-inputs.tf
+++ b/modules/runner-config/tests/fixtures/computed-iam-inputs/computed-iam-inputs.tf
@@ -62,22 +62,32 @@ module "external_iam" {
github = {
app_parameters = {
- key_base64 = {
- name = "/github-runner/key-base64"
- arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64"
- }
- id = {
- name = "/github-runner/app-id"
- arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id"
- }
- additional_apps_manifest = {
- name = "/github-runner/additional-apps-manifest"
- arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/additional-apps-manifest"
- }
- additional_app_parameter_arns = [
- "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/additional-app-id",
- "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/additional-app-key-base64",
- "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/additional-app-installation-id",
+ key_base64 = [
+ {
+ name = "/github-runner/key-base64"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64"
+ },
+ {
+ name = "/github-runner/additional-app-key-base64"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/additional-app-key-base64"
+ },
+ ]
+ id = [
+ {
+ name = "/github-runner/app-id"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id"
+ },
+ {
+ name = "/github-runner/additional-app-id"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/additional-app-id"
+ },
+ ]
+ installation_id = [
+ null,
+ {
+ name = "/github-runner/additional-app-installation-id"
+ arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/additional-app-installation-id"
+ },
]
}
}
@@ -168,14 +178,15 @@ module "generated_policy" {
github = {
app_parameters = {
- key_base64 = {
+ key_base64 = [{
name = "/github-runner/key-base64"
arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64"
- }
- id = {
+ }]
+ id = [{
name = "/github-runner/app-id"
arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id"
- }
+ }]
+ installation_id = [null]
}
}
diff --git a/modules/runner-config/tests/fixtures/computed-iam-inputs/versions.tf b/modules/runner-config/tests/fixtures/computed-iam-inputs/versions.tf
index 688b2a4e03..9fd85fad8f 100644
--- a/modules/runner-config/tests/fixtures/computed-iam-inputs/versions.tf
+++ b/modules/runner-config/tests/fixtures/computed-iam-inputs/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3"
required_providers {
aws = {
diff --git a/modules/runner-config/tests/pool.tftest.hcl b/modules/runner-config/tests/pool.tftest.hcl
index 5639c0d217..12a81854c3 100644
--- a/modules/runner-config/tests/pool.tftest.hcl
+++ b/modules/runner-config/tests/pool.tftest.hcl
@@ -81,8 +81,9 @@ variables {
github = {
app_parameters = {
- key_base64 = { name = "/github-runner/key-base64", arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64" }
- id = { name = "/github-runner/app-id", arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id" }
+ key_base64 = [{ name = "/github-runner/key-base64", arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64" }]
+ id = [{ name = "/github-runner/app-id", arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id" }]
+ installation_id = [null]
}
}
@@ -219,14 +220,8 @@ run "plan_with_pool_enabled" {
}
assert {
- condition = tomap({
- for tag in jsondecode(module.orchestration_webhook[0].scale_up.lambda.environment[0].variables["SSM_PARAMETER_STORE_TAGS"]) :
- tag.Key => tag.Value
- }) == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- })
- error_message = "Runtime Parameter Store tags must include common generated tags without leaking EC2 bootstrap tags."
+ condition = length(jsondecode(module.orchestration_webhook[0].scale_up.lambda.environment[0].variables["SSM_PARAMETER_STORE_TAGS"])) == 0
+ error_message = "Runtime Parameter Store tags must remain empty when no module or SSM tags are configured; EC2 bootstrap tags must not leak into them."
}
assert {
diff --git a/modules/runner-config/tests/tags.tftest.hcl b/modules/runner-config/tests/tags.tftest.hcl
index 509abfebfa..6c004879ba 100644
--- a/modules/runner-config/tests/tags.tftest.hcl
+++ b/modules/runner-config/tests/tags.tftest.hcl
@@ -79,14 +79,15 @@ variables {
github = {
app_parameters = {
- key_base64 = {
+ key_base64 = [{
name = "/github-runner/key-base64"
arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/key-base64"
- }
- id = {
+ }]
+ id = [{
name = "/github-runner/app-id"
arn = "arn:aws:ssm:eu-west-1:123456789012:parameter/github-runner/app-id"
- }
+ }]
+ installation_id = [null]
}
}
@@ -191,178 +192,130 @@ run "layered_component_tags" {
assert {
condition = module.orchestration_webhook[0].scale_up.lambda.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "scale-up"
- module = "yes"
- lambda = "yes"
- scale_up = "yes"
+ precedence = "scale-up"
+ module = "yes"
+ lambda = "yes"
+ scale_up = "yes"
}) && module.orchestration_webhook[0].scale_up.log_group.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "scale-up"
- module = "yes"
- log = "yes"
- scale_up = "yes"
+ precedence = "scale-up"
+ module = "yes"
+ log = "yes"
+ scale_up = "yes"
}) && module.orchestration_webhook[0].scale_up.role.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "scale-up"
- module = "yes"
- scale_up = "yes"
+ precedence = "scale-up"
+ module = "yes"
+ scale_up = "yes"
})
error_message = "Scale-up tags must layer module, shared resource, and component tags with the component taking precedence."
}
assert {
condition = module.orchestration_webhook[0].scale_down.lambda.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "scale-down"
- module = "yes"
- lambda = "yes"
- scale_down = "yes"
+ precedence = "scale-down"
+ module = "yes"
+ lambda = "yes"
+ scale_down = "yes"
}) && module.orchestration_webhook[0].scale_down.log_group.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "scale-down"
- module = "yes"
- log = "yes"
- scale_down = "yes"
+ precedence = "scale-down"
+ module = "yes"
+ log = "yes"
+ scale_down = "yes"
}) && module.orchestration_webhook[0].scale_down.role.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "scale-down"
- module = "yes"
- scale_down = "yes"
+ precedence = "scale-down"
+ module = "yes"
+ scale_down = "yes"
})
error_message = "Scale-down tags must layer module, shared resource, and component tags with the component taking precedence."
}
assert {
condition = aws_iam_role.runner[0].tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "runner"
- module = "yes"
- runner = "yes"
+ precedence = "runner"
+ module = "yes"
+ runner = "yes"
})
error_message = "Runner tags must override module tags on the common runner role."
}
assert {
condition = aws_ssm_parameter.runner_agent_mode.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "ssm-parameter"
- module = "yes"
- ssm = "yes"
- parameter = "yes"
+ precedence = "ssm-parameter"
+ module = "yes"
+ ssm = "yes"
+ parameter = "yes"
}) && tomap({
for tag in jsondecode(module.orchestration_webhook[0].scale_up.lambda.environment[0].variables["SSM_PARAMETER_STORE_TAGS"]) :
tag.Key => tag.Value
}) == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "ssm-parameter"
- module = "yes"
- ssm = "yes"
- parameter = "yes"
+ precedence = "ssm-parameter"
+ module = "yes"
+ ssm = "yes"
+ parameter = "yes"
})
error_message = "Terraform-managed and runtime-created SSM parameters must use the same layered parameter tags."
}
assert {
- condition = tomap(local.ssm_housekeeper_lambda_tags) == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "ssm-housekeeper"
- module = "yes"
- lambda = "yes"
- ssm = "yes"
- housekeeper = "yes"
- })
- error_message = "SSM housekeeper Lambda tags must include generated and layered tags."
- }
-
- assert {
- condition = tomap(local.ssm_housekeeper_log_tags) == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "ssm-housekeeper"
- module = "yes"
- log = "yes"
- ssm = "yes"
- housekeeper = "yes"
+ condition = local.ssm_housekeeper_lambda_tags == tomap({
+ precedence = "ssm-housekeeper"
+ module = "yes"
+ lambda = "yes"
+ ssm = "yes"
+ housekeeper = "yes"
+ }) && local.ssm_housekeeper_log_tags == tomap({
+ precedence = "ssm-housekeeper"
+ module = "yes"
+ log = "yes"
+ ssm = "yes"
+ housekeeper = "yes"
+ }) && local.ssm_housekeeper_tags == tomap({
+ precedence = "ssm-housekeeper"
+ module = "yes"
+ ssm = "yes"
+ housekeeper = "yes"
})
- error_message = "SSM housekeeper log tags must include generated and layered tags."
- }
-
- assert {
- condition = tomap(local.ssm_housekeeper_tags) == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "ssm-housekeeper"
- module = "yes"
- ssm = "yes"
- housekeeper = "yes"
- })
- error_message = "SSM housekeeper resource tags must include generated and layered tags."
+ error_message = "SSM housekeeper tags must layer module, SSM, shared resource, and housekeeper tags."
}
assert {
condition = module.orchestration_webhook[0].pool.lambda.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "pool"
- module = "yes"
- lambda = "yes"
- pool = "yes"
+ precedence = "pool"
+ module = "yes"
+ lambda = "yes"
+ pool = "yes"
}) && module.orchestration_webhook[0].pool.log_group.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "pool"
- module = "yes"
- log = "yes"
- pool = "yes"
+ precedence = "pool"
+ module = "yes"
+ log = "yes"
+ pool = "yes"
}) && module.orchestration_webhook[0].pool.role.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "pool"
- module = "yes"
- pool = "yes"
+ precedence = "pool"
+ module = "yes"
+ pool = "yes"
})
error_message = "Pool tags must layer module, shared resource, and component tags with the component taking precedence."
}
assert {
condition = module.orchestration_webhook[0].job_retry.lambda.function.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "job-retry"
- module = "yes"
- lambda = "yes"
- job_retry = "yes"
+ precedence = "job-retry"
+ module = "yes"
+ lambda = "yes"
+ job_retry = "yes"
}) && module.orchestration_webhook[0].job_retry.lambda.log_group.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "job-retry"
- module = "yes"
- log = "yes"
- job_retry = "yes"
+ precedence = "job-retry"
+ module = "yes"
+ log = "yes"
+ job_retry = "yes"
}) && module.orchestration_webhook[0].job_retry.lambda.role.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "job-retry"
- module = "yes"
- job_retry = "yes"
+ precedence = "job-retry"
+ module = "yes"
+ job_retry = "yes"
}) && module.orchestration_webhook[0].job_retry.queue.tags == tomap({
- Name = "github-actions-action-runner"
- "ghr:ssm_config_path" = "/github-runner/config"
- precedence = "job-retry"
- module = "yes"
- queue = "yes"
- job_retry = "yes"
+ precedence = "job-retry"
+ module = "yes"
+ queue = "yes"
+ job_retry = "yes"
})
error_message = "Job-retry tags must layer module, shared resource, and component tags with the component taking precedence."
}
diff --git a/modules/runner-config/validations.tf b/modules/runner-config/validations.tf
index f510bf0422..0c4ff5bcf0 100644
--- a/modules/runner-config/validations.tf
+++ b/modules/runner-config/validations.tf
@@ -90,7 +90,12 @@ resource "terraform_data" "validate_config" {
for provider_name, provider_config in var.orchestration_provider : provider_name
if provider_config != null
]) == 1
- error_message = "Exactly one orchestration provider must be configured. Supported providers: webhook."
+ error_message = "Exactly one orchestration provider must be configured. Supported providers: webhook and scale_set."
+ }
+
+ precondition {
+ condition = var.orchestration_provider.scale_set == null ? true : local.provider_contract.capabilities.scale_set != null
+ error_message = "The selected compute provider must expose a scale_set capability when scale_set orchestration is selected."
}
precondition {
diff --git a/modules/runner-config/variables.orchestration-provider.tf b/modules/runner-config/variables.orchestration-provider.tf
index 44fa3525fd..4077dc29f7 100644
--- a/modules/runner-config/variables.orchestration-provider.tf
+++ b/modules/runner-config/variables.orchestration-provider.tf
@@ -3,7 +3,8 @@ variable "orchestration_provider" {
description = <<-EOT
Runner demand-orchestration provider configuration. Exactly one provider block must be non-null. Wrapper presence selects the provider and must therefore be known during planning; values inside the selected provider may remain unknown until apply.
- - `webhook`: Selects the workflow-job webhook control plane. It owns runner lifecycle and capacity, the build queue reference, the runner-control artifact, scale-up, scale-down, scheduled pool, and optional job-retry controls. Future providers can be added as sibling blocks without moving this contract.
+ - `webhook`: Selects the workflow-job webhook control plane. It owns runner lifecycle and capacity, the build queue reference, the runner-control artifact, scale-up, scale-down, scheduled pool, and optional job-retry controls.
+ - `scale_set`: Selects scale-set orchestration for this runner config. The multi-runner topology owns the shared controller service and passes this plan-known selection marker to runner-config. Scale-set runners always use ephemeral JIT registration.
- `webhook.runner`: Runner lifecycle, boot timeout, and capacity settings owned by webhook orchestration.
- `webhook.runner.boot_time_in_minutes`: Expected runner boot duration used by scale-down and pool controls. The default is `5`.
- `webhook.runner.ephemeral`: Registers runners in ephemeral mode. The default is `false`.
@@ -30,7 +31,6 @@ variable "orchestration_provider" {
- `webhook.lambda.scale.down.timeout`: Scale-down Lambda timeout in seconds. The default is `60`.
- `webhook.lambda.scale.down.schedule_expression`: EventBridge schedule expression that invokes scale-down. The default is `cron(*/5 * * * ? *)`.
- `webhook.lambda.scale.down.minimum_running_time_in_minutes`: Optional minimum runner age before scale-down may terminate it. The default is null, which selects the operating-system default.
- - `webhook.lambda.scale.down.idle_confirmation_seconds`: Number of seconds a runner must consistently report not-busy before scale-down terminates it. The default is `0`, which preserves the single-reading behavior.
- `webhook.lambda.scale.down.idle_config`: Time-based desired idle-runner configurations. The default is `[]`.
- `webhook.lambda.scale.down.idle_config[].cron`: Cron expression identifying when the idle configuration applies.
- `webhook.lambda.scale.down.idle_config[].timeZone`: IANA time zone used to evaluate the cron expression.
@@ -100,7 +100,6 @@ variable "orchestration_provider" {
timeout = optional(number, 60)
schedule_expression = optional(string, "cron(*/5 * * * ? *)")
minimum_running_time_in_minutes = optional(number, null)
- idle_confirmation_seconds = optional(number, 0)
idle_config = optional(list(object({
cron = string
timeZone = string
@@ -137,6 +136,14 @@ variable "orchestration_provider" {
}), {})
}), {})
}), null)
+ scale_set = optional(object({
+ name = string
+ runner = optional(object({
+ min_runners = optional(number, 0)
+ max_runners = optional(number, 10)
+ boot_time_in_minutes = optional(number, 10)
+ }), {})
+ }), null)
})
nullable = false
diff --git a/modules/runner-config/variables.tf b/modules/runner-config/variables.tf
index 2273dc4769..5928693ade 100644
--- a/modules/runner-config/variables.tf
+++ b/modules/runner-config/variables.tf
@@ -75,23 +75,18 @@ variable "github" {
description = <<-EOT
GitHub API and runner-registration configuration.
- - `app_parameters.key_base64`: Parameter Store reference for the primary GitHub App private key.
- - `app_parameters.id`: Parameter Store reference for the primary GitHub App ID.
- - `app_parameters.additional_apps_manifest`: Optional Parameter Store reference containing the additional GitHub App manifest.
- - `app_parameters.additional_app_parameter_arns`: ARNs of the additional GitHub App credential parameters.
+ - `app_parameters.key_base64`: Ordered Parameter Store references for GitHub App private keys.
+ - `app_parameters.id`: Ordered Parameter Store references for GitHub App IDs.
+ - `app_parameters.installation_id`: Ordered optional Parameter Store references for GitHub App installation IDs.
- `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.
EOT
type = object({
app_parameters = object({
- key_base64 = map(string)
- id = map(string)
- additional_apps_manifest = optional(object({
- name = string
- arn = string
- }), null)
- additional_app_parameter_arns = optional(list(string), [])
+ key_base64 = list(map(string))
+ id = list(map(string))
+ installation_id = list(object({ name = string, arn = string }))
})
enterprise_server = optional(object({
url = optional(string, null)
diff --git a/modules/runner-config/versions.tf b/modules/runner-config/versions.tf
index 0bedc91fd5..3ef011ea0a 100644
--- a/modules/runner-config/versions.tf
+++ b/modules/runner-config/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.4.0"
required_providers {
aws = {
diff --git a/modules/runners/README.md b/modules/runners/README.md
index 4e4f650bc4..627b7b62c9 100644
--- a/modules/runners/README.md
+++ b/modules/runners/README.md
@@ -52,7 +52,7 @@ yarn run dist
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [aws](#requirement\_aws) | >= 6.33 |
## Providers
diff --git a/modules/runners/job-retry/README.md b/modules/runners/job-retry/README.md
index 88ec3bdde0..57c6d9dc91 100644
--- a/modules/runners/job-retry/README.md
+++ b/modules/runners/job-retry/README.md
@@ -12,7 +12,7 @@ The module is an inner module and used by the runner module when the opt-in feat
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [aws](#requirement\_aws) | >= 6.21 |
## Providers
diff --git a/modules/runners/job-retry/versions.tf b/modules/runners/job-retry/versions.tf
index 1238b79cc3..42a40b33fd 100644
--- a/modules/runners/job-retry/versions.tf
+++ b/modules/runners/job-retry/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
diff --git a/modules/runners/pool/README.md b/modules/runners/pool/README.md
index 7bb73a5a58..54b85d968e 100644
--- a/modules/runners/pool/README.md
+++ b/modules/runners/pool/README.md
@@ -10,7 +10,7 @@ The pool is an opt-in feature. To be able to use the count on a module level to
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 0.14.1 |
| [aws](#requirement\_aws) | >= 6.21 |
## Providers
diff --git a/modules/runners/pool/versions.tf b/modules/runners/pool/versions.tf
index 1238b79cc3..bceee0424e 100644
--- a/modules/runners/pool/versions.tf
+++ b/modules/runners/pool/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 0.14.1"
required_providers {
aws = {
diff --git a/modules/runners/templates/start-runner.sh b/modules/runners/templates/start-runner.sh
index b2c6efed1a..7f2c0f82c5 100644
--- a/modules/runners/templates/start-runner.sh
+++ b/modules/runners/templates/start-runner.sh
@@ -95,7 +95,7 @@ cleanup() {
if [ "$exit_code" -ne 0 ]; then
echo "ERROR: runner-start-failed with exit code $exit_code occurred on $error_location"
- create_xray_error_segment "$${SEGMENT:-}" "runner-start-failed with exit code $exit_code occurred on $error_location - $error_lineno"
+ create_xray_error_segment "$SEGMENT" "runner-start-failed with exit code $exit_code occurred on $error_location - $error_lineno"
fi
# allows to flush the cloud watch logs and traces
sleep 10
@@ -260,7 +260,7 @@ if [[ "$enable_jit_config" == "false" || $agent_mode != "ephemeral" ]]; then
tag_instance_with_runner_id
fi
-create_xray_success_segment "$${SEGMENT:-}"
+create_xray_success_segment "$SEGMENT"
if [[ $agent_mode = "ephemeral" ]]; then
echo "Starting the runner in ephemeral mode"
diff --git a/modules/runners/versions.tf b/modules/runners/versions.tf
index 0bedc91fd5..da9769f550 100644
--- a/modules/runners/versions.tf
+++ b/modules/runners/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
diff --git a/modules/setup-iam-permissions/README.md b/modules/setup-iam-permissions/README.md
index f5a8831cb6..f2401278c0 100644
--- a/modules/setup-iam-permissions/README.md
+++ b/modules/setup-iam-permissions/README.md
@@ -41,7 +41,7 @@ Next execute the created Terraform code via `terraform init && terraform apply`.
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [aws](#requirement\_aws) | >= 6.21 |
## Providers
diff --git a/modules/setup-iam-permissions/versions.tf b/modules/setup-iam-permissions/versions.tf
index 1238b79cc3..42a40b33fd 100644
--- a/modules/setup-iam-permissions/versions.tf
+++ b/modules/setup-iam-permissions/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
diff --git a/modules/ssm/README.md b/modules/ssm/README.md
index e35c340071..2750e04f0e 100644
--- a/modules/ssm/README.md
+++ b/modules/ssm/README.md
@@ -9,7 +9,7 @@ This module is used for storing configuration of runners, registration tokens an
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [aws](#requirement\_aws) | >= 6.21 |
## Providers
@@ -31,6 +31,7 @@ No modules.
| [aws_ssm_parameter.additional_github_app_key_base64](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
| [aws_ssm_parameter.additional_github_apps_manifest](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
| [aws_ssm_parameter.github_app_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
+| [aws_ssm_parameter.github_app_installation_id](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
| [aws_ssm_parameter.github_app_key_base64](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
| [aws_ssm_parameter.github_app_webhook_secret](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
@@ -39,7 +40,7 @@ No modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| [additional\_github\_apps](#input\_additional\_github\_apps) | Additional GitHub Apps for distributing API rate limit usage. | list(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 }))
installation_id = optional(string)
installation_id_ssm = optional(object({ arn = string, name = string }))
})) | `[]` | 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 |
+| [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
}))
installation_id = optional(string)
installation_id_ssm = optional(object({
arn = string
name = string
}))
webhook_secret = optional(string)
webhook_secret_ssm = optional(object({
arn = string
name = string
}))
}) | n/a | yes |
| [kms\_key\_arn](#input\_kms\_key\_arn) | Optional CMK Key ARN to be used for Parameter Store. | `string` | `null` | no |
| [path\_prefix](#input\_path\_prefix) | The path prefix used for naming resources | `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 |
diff --git a/modules/ssm/outputs.tf b/modules/ssm/outputs.tf
index e1afaf990e..f37b28d939 100644
--- a/modules/ssm/outputs.tf
+++ b/modules/ssm/outputs.tf
@@ -8,6 +8,10 @@ output "parameters" {
name = var.github_app.key_base64_ssm != null ? var.github_app.key_base64_ssm.name : aws_ssm_parameter.github_app_key_base64[0].name
arn = var.github_app.key_base64_ssm != null ? var.github_app.key_base64_ssm.arn : aws_ssm_parameter.github_app_key_base64[0].arn
}
+ github_app_installation_id = var.github_app.installation_id_ssm != null || var.github_app.installation_id != null ? {
+ name = var.github_app.installation_id_ssm != null ? var.github_app.installation_id_ssm.name : aws_ssm_parameter.github_app_installation_id[0].name
+ arn = var.github_app.installation_id_ssm != null ? var.github_app.installation_id_ssm.arn : aws_ssm_parameter.github_app_installation_id[0].arn
+ } : null
github_app_webhook_secret = {
name = var.github_app.webhook_secret_ssm != null ? var.github_app.webhook_secret_ssm.name : aws_ssm_parameter.github_app_webhook_secret[0].name
arn = var.github_app.webhook_secret_ssm != null ? var.github_app.webhook_secret_ssm.arn : aws_ssm_parameter.github_app_webhook_secret[0].arn
diff --git a/modules/ssm/ssm.tf b/modules/ssm/ssm.tf
index 9467a136e5..ab8a406f2d 100644
--- a/modules/ssm/ssm.tf
+++ b/modules/ssm/ssm.tf
@@ -16,6 +16,15 @@ resource "aws_ssm_parameter" "github_app_key_base64" {
tags = var.tags
}
+resource "aws_ssm_parameter" "github_app_installation_id" {
+ count = var.github_app.installation_id_ssm != null || var.github_app.installation_id == null ? 0 : 1
+ name = "${var.path_prefix}/github_app_installation_id"
+ type = "SecureString"
+ value = var.github_app.installation_id
+ key_id = local.kms_key_arn
+ tags = var.tags
+}
+
resource "aws_ssm_parameter" "github_app_webhook_secret" {
count = var.github_app.webhook_secret_ssm != null ? 0 : 1
name = "${var.path_prefix}/github_app_webhook_secret"
diff --git a/modules/ssm/variables.tf b/modules/ssm/variables.tf
index d7387ecc30..d1c0f41f36 100644
--- a/modules/ssm/variables.tf
+++ b/modules/ssm/variables.tf
@@ -17,6 +17,11 @@ variable "github_app" {
arn = string
name = string
}))
+ installation_id = optional(string)
+ installation_id_ssm = optional(object({
+ arn = string
+ name = string
+ }))
webhook_secret = optional(string)
webhook_secret_ssm = optional(object({
arn = string
diff --git a/modules/ssm/versions.tf b/modules/ssm/versions.tf
index 1238b79cc3..42a40b33fd 100644
--- a/modules/ssm/versions.tf
+++ b/modules/ssm/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
diff --git a/modules/termination-watcher/README.md b/modules/termination-watcher/README.md
index 89a488658a..4cdf37f13b 100644
--- a/modules/termination-watcher/README.md
+++ b/modules/termination-watcher/README.md
@@ -60,7 +60,7 @@ yarn run dist
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [aws](#requirement\_aws) | >= 6.21 |
## Providers
diff --git a/modules/termination-watcher/notification/README.md b/modules/termination-watcher/notification/README.md
index c6feff297f..31df74586f 100644
--- a/modules/termination-watcher/notification/README.md
+++ b/modules/termination-watcher/notification/README.md
@@ -3,7 +3,7 @@
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [aws](#requirement\_aws) | >= 6.21 |
## Providers
@@ -42,4 +42,4 @@
| Name | Description |
|------|-------------|
| [lambda](#output\_lambda) | n/a |
-
+
\ No newline at end of file
diff --git a/modules/termination-watcher/notification/versions.tf b/modules/termination-watcher/notification/versions.tf
index 1238b79cc3..42a40b33fd 100644
--- a/modules/termination-watcher/notification/versions.tf
+++ b/modules/termination-watcher/notification/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
diff --git a/modules/termination-watcher/termination/README.md b/modules/termination-watcher/termination/README.md
index 77d37d91aa..32b32aa54e 100644
--- a/modules/termination-watcher/termination/README.md
+++ b/modules/termination-watcher/termination/README.md
@@ -3,7 +3,7 @@
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [aws](#requirement\_aws) | >= 6.21 |
## Providers
@@ -39,4 +39,4 @@
| Name | Description |
|------|-------------|
| [lambda](#output\_lambda) | n/a |
-
+
\ No newline at end of file
diff --git a/modules/termination-watcher/termination/versions.tf b/modules/termination-watcher/termination/versions.tf
index 1238b79cc3..42a40b33fd 100644
--- a/modules/termination-watcher/termination/versions.tf
+++ b/modules/termination-watcher/termination/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
diff --git a/modules/termination-watcher/versions.tf b/modules/termination-watcher/versions.tf
index 1238b79cc3..42a40b33fd 100644
--- a/modules/termination-watcher/versions.tf
+++ b/modules/termination-watcher/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
diff --git a/modules/webhook-github-app/README.md b/modules/webhook-github-app/README.md
index 66505f2ecb..6de85ee30d 100644
--- a/modules/webhook-github-app/README.md
+++ b/modules/webhook-github-app/README.md
@@ -11,7 +11,7 @@ This module updates the GitHub App webhook with the endpoint and secret and can
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [null](#requirement\_null) | ~> 3 |
## Providers
diff --git a/modules/webhook-github-app/versions.tf b/modules/webhook-github-app/versions.tf
index ad5dce45d4..e0632ba7df 100644
--- a/modules/webhook-github-app/versions.tf
+++ b/modules/webhook-github-app/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
null = {
diff --git a/modules/webhook/README.md b/modules/webhook/README.md
index f6a752da2c..70121458a7 100644
--- a/modules/webhook/README.md
+++ b/modules/webhook/README.md
@@ -35,7 +35,7 @@ yarn run dist
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [aws](#requirement\_aws) | >= 6.21 |
| [null](#requirement\_null) | ~> 3 |
diff --git a/modules/webhook/direct/README.md b/modules/webhook/direct/README.md
index e4735db39d..d639ed6398 100644
--- a/modules/webhook/direct/README.md
+++ b/modules/webhook/direct/README.md
@@ -3,7 +3,7 @@
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [aws](#requirement\_aws) | >= 6.21 |
| [null](#requirement\_null) | ~> 3.2 |
@@ -48,4 +48,4 @@ No modules.
|------|-------------|
| [webhook](#output\_webhook) | n/a |
| [webhook\_lambda\_function](#output\_webhook\_lambda\_function) | n/a |
-
+
\ No newline at end of file
diff --git a/modules/webhook/direct/versions.tf b/modules/webhook/direct/versions.tf
index 98ffc58edc..82776fc618 100644
--- a/modules/webhook/direct/versions.tf
+++ b/modules/webhook/direct/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
diff --git a/modules/webhook/eventbridge/README.md b/modules/webhook/eventbridge/README.md
index 1159745b4f..07aa0bdd61 100644
--- a/modules/webhook/eventbridge/README.md
+++ b/modules/webhook/eventbridge/README.md
@@ -3,7 +3,7 @@
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 1.5.6 |
+| [terraform](#requirement\_terraform) | >= 1.3.0 |
| [aws](#requirement\_aws) | >= 6.21 |
| [null](#requirement\_null) | ~> 3.2 |
@@ -63,4 +63,4 @@ No modules.
| [dispatcher](#output\_dispatcher) | n/a |
| [eventbridge](#output\_eventbridge) | n/a |
| [webhook](#output\_webhook) | n/a |
-
+
\ No newline at end of file
diff --git a/modules/webhook/eventbridge/versions.tf b/modules/webhook/eventbridge/versions.tf
index 98ffc58edc..82776fc618 100644
--- a/modules/webhook/eventbridge/versions.tf
+++ b/modules/webhook/eventbridge/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
diff --git a/modules/webhook/versions.tf b/modules/webhook/versions.tf
index 651075f9ce..e864c4f9ed 100644
--- a/modules/webhook/versions.tf
+++ b/modules/webhook/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {
diff --git a/scripts/migrate_multi_runner_state.py b/scripts/migrate_multi_runner_state.py
deleted file mode 100644
index 065bf0c72c..0000000000
--- a/scripts/migrate_multi_runner_state.py
+++ /dev/null
@@ -1,329 +0,0 @@
-#!/usr/bin/env python3
-"""Move v1 multi-runner state into the v2 runner-config topology.
-
-The migration mapping table contains relative, unkeyed mappings. The actual
-state contains one module instance per dynamic multi-runner key, for example:
-
- module.runners["large"].aws_iam_role.runner[0]
-
-This script expands every mapping for every key found in the current state and
-then optionally runs state mv. It is deliberately a dry run unless --apply
-is supplied.
-"""
-
-from __future__ import annotations
-
-import argparse
-import os
-import re
-import subprocess
-import sys
-from dataclasses import dataclass
-from pathlib import Path
-from typing import Iterable
-
-
-MODULE_KEY_RE = re.compile(r'module\.runners(\["(?:\\.|[^"])*"\])')
-INSTANCE_SUFFIX_RE = r'(?P(?:\[[^]]+\])*)$'
-MULTI_RUNNER_MODULE_PREFIX = "module.runners."
-
-# These are the relative addresses from the v1 module call to the v2 module
-# call. The runner key is inserted after module.runners/module.runner_configs
-# at runtime because it is a user-controlled for_each key.
-MIGRATION_MAPPINGS = (
- ('module.runners.aws_iam_role.runner', 'module.runner_configs.aws_iam_role.runner'),
- ('module.runners.aws_ssm_parameter.runner_agent_mode', 'module.runner_configs.aws_ssm_parameter.runner_agent_mode'),
- ('module.runners.aws_ssm_parameter.disable_default_labels', 'module.runner_configs.aws_ssm_parameter.disable_default_labels'),
- ('module.runners.aws_ssm_parameter.jit_config_enabled', 'module.runner_configs.aws_ssm_parameter.jit_config_enabled'),
- ('module.runners.aws_ssm_parameter.token_path', 'module.runner_configs.aws_ssm_parameter.token_path'),
- ('module.runners.aws_iam_policy.ami_id_ssm_parameter_read', 'module.runner_configs.module.compute_aws_ec2[0].aws_iam_policy.ami_id_ssm_parameter_read'),
- ('module.runners.aws_iam_instance_profile.runner', 'module.runner_configs.module.compute_aws_ec2[0].aws_iam_instance_profile.runner'),
- ('module.runners.aws_ssm_parameter.cloudwatch_agent_config_runner', 'module.runner_configs.module.compute_aws_ec2[0].aws_ssm_parameter.cloudwatch_agent_config_runner'),
- ('module.runners.aws_cloudwatch_log_group.gh_runners', 'module.runner_configs.module.compute_aws_ec2[0].aws_cloudwatch_log_group.gh_runners'),
- ('module.runners.aws_iam_role_policy.cloudwatch[0]', 'module.runner_configs.aws_iam_role_policy.runner_provider["cloudwatch"]'),
- ('module.runners.aws_ssm_parameter.runner_ami_id', 'module.runner_configs.module.compute_aws_ec2[0].aws_ssm_parameter.runner_ami_id'),
- ('module.runners.aws_launch_template.runner', 'module.runner_configs.module.compute_aws_ec2[0].aws_launch_template.runner'),
- ('module.runners.aws_security_group.runner_sg', 'module.runner_configs.module.compute_aws_ec2[0].aws_security_group.runner_sg'),
- ('module.runners.aws_ssm_parameter.runner_config_run_as', 'module.runner_configs.module.compute_aws_ec2[0].aws_ssm_parameter.runner_config_run_as'),
- ('module.runners.aws_ssm_parameter.runner_enable_cloudwatch', 'module.runner_configs.module.compute_aws_ec2[0].aws_ssm_parameter.runner_enable_cloudwatch'),
- ('module.runners.aws_iam_role_policy.runner_session_manager_aws_managed[0]', 'module.runner_configs.aws_iam_role_policy.runner_provider["session_manager"]'),
- ('module.runners.aws_iam_role_policy.ssm_parameters[0]', 'module.runner_configs.aws_iam_role_policy.runner_provider["ssm_parameters"]'),
- ('module.runners.aws_iam_role_policy.dist_bucket[0]', 'module.runner_configs.aws_iam_role_policy.runner_provider["distribution_bucket"]'),
- ('module.runners.aws_iam_role_policy.describe_tags[0]', 'module.runner_configs.aws_iam_role_policy.runner_provider["describe_tags"]'),
- ('module.runners.aws_iam_role_policy.create_tag[0]', 'module.runner_configs.aws_iam_role_policy.runner_provider["create_tags"]'),
- ('module.runners.aws_iam_role_policy.ec2[0]', 'module.runner_configs.aws_iam_role_policy.runner_provider["terminate_self"]'),
- ('module.runners.aws_iam_role_policy_attachment.xray_tracing[0]', 'module.runner_configs.aws_iam_role_policy_attachment.runner["xray"]'),
- ('module.runners.module.pool[0].aws_lambda_function.pool', 'module.runner_configs.module.orchestration_webhook[0].module.pool[0].aws_lambda_function.pool'),
- ('module.runners.module.pool[0].aws_cloudwatch_log_group.pool', 'module.runner_configs.module.orchestration_webhook[0].module.pool[0].aws_cloudwatch_log_group.pool'),
- ('module.runners.module.pool[0].aws_iam_role.pool', 'module.runner_configs.module.orchestration_webhook[0].module.pool[0].aws_iam_role.pool'),
- ('module.runners.module.pool[0].aws_iam_role_policy.pool', 'module.runner_configs.module.orchestration_webhook[0].module.pool[0].aws_iam_role_policy.pool'),
- ('module.runners.module.pool[0].aws_iam_role_policy.pool_logging', 'module.runner_configs.module.orchestration_webhook[0].module.pool[0].aws_iam_role_policy.pool_logging'),
- ('module.runners.module.pool[0].aws_iam_role_policy_attachment.pool_vpc_execution_role', 'module.runner_configs.module.orchestration_webhook[0].module.pool[0].aws_iam_role_policy_attachment.pool_vpc_execution_role'),
- ('module.runners.module.pool[0].aws_iam_role_policy_attachment.ami_id_ssm_parameter_read', 'module.runner_configs.module.orchestration_webhook[0].module.pool[0].aws_iam_role_policy_attachment.provider'),
- ('module.runners.module.pool[0].aws_iam_role_policy.pool_xray', 'module.runner_configs.module.orchestration_webhook[0].module.pool[0].aws_iam_role_policy.pool_xray'),
- ('module.runners.module.pool[0].aws_scheduler_schedule_group.pool', 'module.runner_configs.module.orchestration_webhook[0].module.pool[0].aws_scheduler_schedule_group.pool'),
- ('module.runners.module.pool[0].aws_iam_role.scheduler', 'module.runner_configs.module.orchestration_webhook[0].module.pool[0].aws_iam_role.scheduler'),
- ('module.runners.module.pool[0].aws_iam_role_policy.scheduler', 'module.runner_configs.module.orchestration_webhook[0].module.pool[0].aws_iam_role_policy.scheduler'),
- ('module.runners.module.pool[0].aws_scheduler_schedule.pool', 'module.runner_configs.module.orchestration_webhook[0].module.pool[0].aws_scheduler_schedule.pool'),
- ('module.runners.aws_lambda_function.scale_up', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_lambda_function.scale_up'),
- ('module.runners.aws_cloudwatch_log_group.scale_up', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_cloudwatch_log_group.scale_up'),
- ('module.runners.aws_lambda_event_source_mapping.scale_up', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_lambda_event_source_mapping.scale_up'),
- ('module.runners.aws_lambda_permission.scale_runners_lambda', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_lambda_permission.scale_runners_lambda'),
- ('module.runners.aws_iam_role.scale_up', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role.scale_up'),
- ('module.runners.aws_iam_role_policy.scale_up', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role_policy.scale_up'),
- ('module.runners.aws_iam_role_policy.scale_up_logging', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role_policy.scale_up_logging'),
- ('module.runners.aws_iam_role_policy.service_linked_role', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role_policy.service_linked_role'),
- ('module.runners.aws_iam_role_policy_attachment.scale_up_vpc_execution_role', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role_policy_attachment.scale_up_vpc_execution_role'),
- ('module.runners.aws_iam_role_policy_attachment.ami_id_ssm_parameter_read', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role_policy_attachment.provider'),
- ('module.runners.aws_iam_role_policy.scale_up_xray', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role_policy.scale_up_xray'),
- ('module.runners.aws_iam_role_policy.job_retry_sqs_publish', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role_policy.job_retry_sqs_publish'),
- ('module.runners.aws_lambda_function.scale_down', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_lambda_function.scale_down'),
- ('module.runners.aws_cloudwatch_log_group.scale_down', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_cloudwatch_log_group.scale_down'),
- ('module.runners.aws_cloudwatch_event_rule.scale_down', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_cloudwatch_event_rule.scale_down'),
- ('module.runners.aws_cloudwatch_event_target.scale_down', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_cloudwatch_event_target.scale_down'),
- ('module.runners.aws_lambda_permission.scale_down', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_lambda_permission.scale_down'),
- ('module.runners.aws_iam_role.scale_down', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role.scale_down'),
- ('module.runners.aws_iam_role_policy.scale_down', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role_policy.scale_down'),
- ('module.runners.aws_iam_role_policy.scale_down_logging', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role_policy.scale_down_logging'),
- ('module.runners.aws_iam_role_policy_attachment.scale_down_vpc_execution_role', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role_policy_attachment.scale_down_vpc_execution_role'),
- ('module.runners.aws_iam_role_policy.scale_down_xray', 'module.runner_configs.module.orchestration_webhook[0].module.scale_runners.aws_iam_role_policy.scale_down_xray'),
- ('module.runners.module.job_retry[0].aws_sqs_queue_policy.job_retry_check_queue_policy', 'module.runner_configs.module.orchestration_webhook[0].module.job_retry[0].aws_sqs_queue_policy.job_retry_check_queue_policy'),
- ('module.runners.module.job_retry[0].aws_sqs_queue.job_retry_check_queue', 'module.runner_configs.module.orchestration_webhook[0].module.job_retry[0].aws_sqs_queue.job_retry_check_queue'),
- ('module.runners.module.job_retry[0].module.job_retry.aws_lambda_function.main', 'module.runner_configs.module.orchestration_webhook[0].module.job_retry[0].aws_lambda_function.job_retry'),
- ('module.runners.module.job_retry[0].module.job_retry.aws_cloudwatch_log_group.main', 'module.runner_configs.module.orchestration_webhook[0].module.job_retry[0].aws_cloudwatch_log_group.job_retry'),
- ('module.runners.module.job_retry[0].module.job_retry.aws_iam_role.main', 'module.runner_configs.module.orchestration_webhook[0].module.job_retry[0].aws_iam_role.job_retry'),
- ('module.runners.module.job_retry[0].module.job_retry.aws_iam_role_policy.lambda_logging', 'module.runner_configs.module.orchestration_webhook[0].module.job_retry[0].aws_iam_role_policy.job_retry_logging'),
- ('module.runners.module.job_retry[0].module.job_retry.aws_iam_role_policy_attachment.vpc_execution_role', 'module.runner_configs.module.orchestration_webhook[0].module.job_retry[0].aws_iam_role_policy_attachment.job_retry_vpc_execution_role'),
- ('module.runners.module.job_retry[0].module.job_retry.aws_iam_role_policy.xray', 'module.runner_configs.module.orchestration_webhook[0].module.job_retry[0].aws_iam_role_policy.job_retry_xray'),
- ('module.runners.module.job_retry[0].aws_lambda_event_source_mapping.job_retry', 'module.runner_configs.module.orchestration_webhook[0].module.job_retry[0].aws_lambda_event_source_mapping.job_retry'),
- ('module.runners.module.job_retry[0].aws_lambda_permission.job_retry', 'module.runner_configs.module.orchestration_webhook[0].module.job_retry[0].aws_lambda_permission.job_retry'),
- ('module.runners.module.job_retry[0].aws_iam_role_policy.job_retry', 'module.runner_configs.module.orchestration_webhook[0].module.job_retry[0].aws_iam_role_policy.job_retry'),
- ('module.runners.aws_lambda_function.ssm_housekeeper', 'module.runner_configs.module.ssm_housekeeper.aws_lambda_function.ssm_housekeeper'),
- ('module.runners.aws_cloudwatch_log_group.ssm_housekeeper', 'module.runner_configs.module.ssm_housekeeper.aws_cloudwatch_log_group.ssm_housekeeper'),
- ('module.runners.aws_cloudwatch_event_rule.ssm_housekeeper', 'module.runner_configs.module.ssm_housekeeper.aws_cloudwatch_event_rule.ssm_housekeeper'),
- ('module.runners.aws_cloudwatch_event_target.ssm_housekeeper', 'module.runner_configs.module.ssm_housekeeper.aws_cloudwatch_event_target.ssm_housekeeper'),
- ('module.runners.aws_lambda_permission.ssm_housekeeper', 'module.runner_configs.module.ssm_housekeeper.aws_lambda_permission.ssm_housekeeper'),
- ('module.runners.aws_iam_role.ssm_housekeeper', 'module.runner_configs.module.ssm_housekeeper.aws_iam_role.ssm_housekeeper'),
- ('module.runners.aws_iam_role_policy.ssm_housekeeper', 'module.runner_configs.module.ssm_housekeeper.aws_iam_role_policy.ssm_housekeeper'),
- ('module.runners.aws_iam_role_policy.ssm_housekeeper_logging', 'module.runner_configs.module.ssm_housekeeper.aws_iam_role_policy.ssm_housekeeper_logging'),
- ('module.runners.aws_iam_role_policy_attachment.ssm_housekeeper_vpc_execution_role', 'module.runner_configs.module.ssm_housekeeper.aws_iam_role_policy_attachment.ssm_housekeeper_vpc_execution_role'),
- ('module.runners.aws_iam_role_policy.ssm_housekeeper_xray', 'module.runner_configs.module.ssm_housekeeper.aws_iam_role_policy.ssm_housekeeper_xray'),
-)
-
-# These resources are outside the dynamic runner-key modules and therefore
-# must be moved once, without inserting a runner key into their addresses.
-STATIC_MIGRATION_MAPPINGS = (
- (
- 'module.runners.terraform_data.validate_v1[0]',
- 'module.runners.terraform_data.validate_v2[0]',
- ),
-)
-
-
-@dataclass(frozen=True)
-class Mapping:
- source: str
- target: str
-
-
-@dataclass(frozen=True)
-class Move:
- source: str
- target: str
-
-
-def parse_args() -> argparse.Namespace:
- parser = argparse.ArgumentParser(
- description=(
- "Expand multi-runner migration mappings for dynamic state keys "
- "and optionally run terraform/terragrunt state mv."
- )
- )
- parser.add_argument(
- "--working-directory",
- type=Path,
- default=Path.cwd(),
- help="Terraform/Terragrunt working directory (default: current directory).",
- )
- parser.add_argument(
- "--tool",
- default="terragrunt",
- help="State command to run: terragrunt, terraform, or tofu (default: terragrunt).",
- )
- parser.add_argument(
- "--apply",
- action="store_true",
- help="Execute the generated state mv commands. Without this, only a plan is printed.",
- )
- parser.add_argument(
- "--yes",
- action="store_true",
- help="Skip the confirmation prompt when --apply is supplied.",
- )
- parser.add_argument(
- "--backup",
- type=Path,
- help="Optional path for a state pull backup before any moves are executed.",
- )
- return parser.parse_args()
-
-
-def command(tool: str, args: list[str], working_directory: Path) -> str:
- completed = subprocess.run(
- [tool, *args],
- cwd=working_directory,
- check=False,
- text=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- if completed.returncode != 0:
- details = completed.stderr.strip() or completed.stdout.strip()
- raise RuntimeError(
- f"{tool} {' '.join(args)} failed with exit status "
- f"{completed.returncode}: {details}"
- )
- return completed.stdout
-
-
-def state_addresses(tool: str, working_directory: Path) -> list[str]:
- output = command(tool, ["state", "list"], working_directory)
- return [line.strip() for line in output.splitlines() if line.strip()]
-
-
-def key_refs(addresses: Iterable[str]) -> list[str]:
- refs = {
- match.group(1)
- for address in addresses
- for match in MODULE_KEY_RE.finditer(address)
- }
- return sorted(refs)
-
-
-def keyed_mapping(mapping: Mapping, key_ref: str) -> Mapping:
- source = MULTI_RUNNER_MODULE_PREFIX + mapping.source.replace(
- "module.runners", f"module.runners{key_ref}", 1
- )
- target = MULTI_RUNNER_MODULE_PREFIX + mapping.target.replace(
- "module.runner_configs", f"module.runner_configs{key_ref}", 1
- )
- return Mapping(source, target)
-
-
-def expand_moves(mappings: Iterable[Mapping], addresses: Iterable[str]) -> list[Move]:
- addresses = list(addresses)
- moves: list[Move] = []
- seen: set[tuple[str, str]] = set()
-
- for key_ref in key_refs(addresses):
- for mapping in mappings:
- expanded = keyed_mapping(mapping, key_ref)
- pattern = re.compile(re.escape(expanded.source) + INSTANCE_SUFFIX_RE)
- for address in addresses:
- match = pattern.search(address)
- if not match:
- continue
- prefix = address[: match.start()]
- target = f"{prefix}{expanded.target}{match.group('instances')}"
- pair = (address, target)
- if pair not in seen:
- moves.append(Move(address, target))
- seen.add(pair)
- return moves
-
-
-def expand_static_moves(mappings: Iterable[Mapping], addresses: Iterable[str]) -> list[Move]:
- address_set = set(addresses)
- return [
- Move(mapping.source, mapping.target)
- for mapping in mappings
- if mapping.source in address_set
- ]
-
-
-def pull_backup(tool: str, working_directory: Path, path: Path) -> None:
- if path.exists():
- raise RuntimeError(f"refusing to overwrite existing backup: {path}")
- path.parent.mkdir(parents=True, exist_ok=True)
- completed = subprocess.run(
- [tool, "state", "pull"],
- cwd=working_directory,
- check=False,
- text=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- if completed.returncode != 0:
- details = completed.stderr.strip() or completed.stdout.strip()
- raise RuntimeError(f"{tool} state pull failed: {details}")
- path.write_text(completed.stdout, encoding="utf-8")
- os.chmod(path, 0o600)
- print(f"State backup written to {path}")
-
-
-def main() -> int:
- args = parse_args()
- working_directory = args.working_directory.resolve()
-
- if not working_directory.is_dir():
- print(f"working directory not found: {working_directory}", file=sys.stderr)
- return 2
-
- try:
- mappings = [Mapping(source, target) for source, target in MIGRATION_MAPPINGS]
- static_mappings = [
- Mapping(source, target) for source, target in STATIC_MIGRATION_MAPPINGS
- ]
- addresses = state_addresses(args.tool, working_directory)
- except (OSError, RuntimeError, ValueError) as error:
- print(str(error), file=sys.stderr)
- return 2
-
- moves = expand_moves(mappings, addresses)
- moves.extend(expand_static_moves(static_mappings, addresses))
- address_set = set(addresses)
- conflicts = [move for move in moves if move.target in address_set]
-
- print(f"Found {len(key_refs(addresses))} runner key(s).")
- print(f"Found {len(mappings) + len(static_mappings)} migration mapping(s).")
- print(f"Generated {len(moves)} state move(s).")
- if not moves:
- print("No old keyed addresses matched the current state.")
- return 1
-
- if conflicts:
- print("Refusing to continue because target addresses already exist:", file=sys.stderr)
- for move in conflicts:
- print(f" {move.source} -> {move.target}", file=sys.stderr)
- return 2
-
- for move in moves:
- print(f" {move.source} -> {move.target}")
-
- if not args.apply:
- print("Dry run only. Re-run with --apply after reviewing the mappings.")
- return 0
-
- if not args.yes:
- answer = input("Execute these state moves? Type 'move' to continue: ")
- if answer != "move":
- print("Aborted.")
- return 1
-
- try:
- if args.backup:
- pull_backup(args.tool, working_directory, args.backup.resolve())
- for move in moves:
- command(args.tool, ["state", "mv", move.source, move.target], working_directory)
- print(f"Moved {move.source} -> {move.target}")
- except (OSError, RuntimeError) as error:
- print(str(error), file=sys.stderr)
- print("Migration stopped. Review state before retrying.", file=sys.stderr)
- return 2
-
- print("State migration completed. Run the Terraform plan again.")
- return 0
-
-
-if __name__ == "__main__":
- raise SystemExit(main())
diff --git a/tests/ministack/README.md b/tests/ministack/README.md
index 3e6cdc82fa..706b5157a2 100644
--- a/tests/ministack/README.md
+++ b/tests/ministack/README.md
@@ -1,7 +1,8 @@
# MiniStack example tests
The MiniStack workflow runs the `base`, `prebuilt`, `default`, `ephemeral`,
-`multi-runner`, `multi-runner-v2`, and `termination-watcher` examples directly
+`multi-runner`, `multi-runner-v2`, `multi-runner-scale-set`, and
+`termination-watcher` examples directly
with Terraform 1.5.6 and the latest Terraform release, and with OpenTofu 1.11
and the latest OpenTofu release.
The examples with input variables get their inputs from their own tfvars files
@@ -10,8 +11,8 @@ and uses the configuration checked into the example itself. No override files,
setup module, or Terraform fixture configuration is checked in. The helper
creates and removes a temporary AMI override for `default` and
`ephemeral`, temporary SSM parameters for `multi-runner`, and temporary AMI
-fixtures for `multi-runner-v2`. The migration test uses its dedicated
-`run-migration-test.sh` lifecycle script.
+fixtures for `multi-runner-v2` and `multi-runner-scale-set`. The migration test
+uses its dedicated `run-migration-test.sh` lifecycle script.
Start MiniStack, set the AWS endpoint and test credentials, then run:
@@ -28,6 +29,8 @@ tests/ministack/run-example.sh apply multi-runner
# or
tests/ministack/run-example.sh apply multi-runner-v2
# or
+tests/ministack/run-example.sh apply multi-runner-scale-set
+# or
tests/ministack/run-example.sh apply termination-watcher
```
@@ -36,8 +39,8 @@ ZIP fixtures in the paths expected by the modules when they are absent, and
removes only the files it created. For `prebuilt`, it seeds AMI metadata through
MiniStack's AWS-compatible EC2 API, then removes only the resources it created
during cleanup. MiniStack v1.5.11 provides the EC2 image behavior needed by the
-`default`, `ephemeral`, and `multi-runner` examples, so they are included in
-the same lifecycle matrix.
+`default`, `ephemeral`, `multi-runner`, and `multi-runner-scale-set` examples,
+so they are included in the same lifecycle matrix.
## Webhook and runner lifecycle smoke test
diff --git a/tests/ministack/multi-runner-scale-set.tfvars b/tests/ministack/multi-runner-scale-set.tfvars
new file mode 100644
index 0000000000..67bc39ea7a
--- /dev/null
+++ b/tests/ministack/multi-runner-scale-set.tfvars
@@ -0,0 +1,50 @@
+environment = "ministack-scale-set"
+aws_region = "eu-west-1"
+
+github_app = {
+ id = "0"
+ key_base64 = "ministack-invalid-key"
+ installation_id = "1"
+}
+
+runner_binaries_enabled = false
+
+ami = {
+ "linux-arm64" = {
+ filter = {
+ name = ["ministack-scale-set-linux-arm64"]
+ state = ["available"]
+ }
+ owners = ["self"]
+ }
+ "linux-x64" = {
+ filter = {
+ name = ["ministack-scale-set-linux-x64"]
+ state = ["available"]
+ }
+ owners = ["self"]
+ }
+ "linux-scale-set" = {
+ filter = {
+ name = ["ministack-scale-set-linux-x64"]
+ state = ["available"]
+ }
+ owners = ["self"]
+ }
+ "windows-x64" = {
+ filter = {
+ name = ["ministack-scale-set-windows-x64"]
+ state = ["available"]
+ }
+ owners = ["self"]
+ }
+}
+
+scale_set = {
+ config_url = "https://github.com/example"
+ name = "ministack-scale-set"
+ id = 1
+ runner_group_id = 1
+ runner_owner = "example"
+ runner_registration_level = "organization"
+}
diff --git a/tests/ministack/multi-runner-v2.tfvars b/tests/ministack/multi-runner-v2.tfvars
index 0f9c6073fc..de54e291cf 100644
--- a/tests/ministack/multi-runner-v2.tfvars
+++ b/tests/ministack/multi-runner-v2.tfvars
@@ -6,6 +6,8 @@ github_app = {
key_base64 = "ministack-invalid-key"
}
+runner_binaries_enabled = false
+
ami = {
"linux-arm64" = {
filter = {
diff --git a/tests/ministack/run-example.sh b/tests/ministack/run-example.sh
index 961b3a8cab..ab16343d95 100755
--- a/tests/ministack/run-example.sh
+++ b/tests/ministack/run-example.sh
@@ -23,7 +23,7 @@ case "$iac_binary" in
esac
case "$example" in
- base | prebuilt | default | ephemeral | multi-runner | multi-runner-v2)
+ base | prebuilt | default | ephemeral | multi-runner | multi-runner-v2 | multi-runner-scale-set)
use_tfvars=true
;;
migration-test)
@@ -33,7 +33,7 @@ case "$example" in
use_tfvars=false
;;
*)
- echo "Supported examples for the runner are: base, prebuilt, default, ephemeral, multi-runner, multi-runner-v2, migration-test, termination-watcher" >&2
+ echo "Supported examples for the runner are: base, prebuilt, default, ephemeral, multi-runner, multi-runner-v2, multi-runner-scale-set, migration-test, termination-watcher" >&2
exit 64
;;
esac
@@ -41,7 +41,7 @@ esac
case "$action" in
init | plan | apply | destroy) ;;
*)
- echo "Usage: $0 {init|plan|apply|destroy} {base|prebuilt|default|ephemeral|multi-runner|multi-runner-v2|migration-test|termination-watcher} [TFVARS_FILE]" >&2
+ echo "Usage: $0 {init|plan|apply|destroy} {base|prebuilt|default|ephemeral|multi-runner|multi-runner-v2|multi-runner-scale-set|migration-test|termination-watcher} [TFVARS_FILE]" >&2
exit 64
;;
esac
@@ -325,6 +325,11 @@ $lambda_zip"
create_ami_fixture "ministack-v2-linux-x64" x86_64 >/dev/null
create_ami_fixture "ministack-v2-windows-x64" x86_64 >/dev/null
;;
+ multi-runner-scale-set)
+ create_ami_fixture "ministack-scale-set-linux-x64" x86_64 >/dev/null
+ create_ami_fixture "ministack-scale-set-linux-arm64" arm64 >/dev/null
+ create_ami_fixture "ministack-scale-set-windows-x64" x86_64 >/dev/null
+ ;;
esac
}
diff --git a/tests/ministack/run-migration-test.sh b/tests/ministack/run-migration-test.sh
deleted file mode 100755
index b5ab9c0786..0000000000
--- a/tests/ministack/run-migration-test.sh
+++ /dev/null
@@ -1,295 +0,0 @@
-#!/bin/sh
-
-set -eu
-
-export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-000000000000}"
-export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-test-only}"
-export AWS_DEFAULT_REGION="${AWS_DEFAULT_REGION:-eu-west-1}"
-export AWS_REGION="${AWS_REGION:-eu-west-1}"
-export AWS_ENDPOINT_URL="${AWS_ENDPOINT_URL:-http://localhost:4566}"
-export AWS_EC2_METADATA_DISABLED="${AWS_EC2_METADATA_DISABLED:-true}"
-
-action="${1:-}"
-iac_binary="${IAC_BINARY:-terraform}"
-
-case "$iac_binary" in
- terraform | tofu) ;;
- *)
- echo "Supported IaC binaries are: terraform, tofu" >&2
- exit 64
- ;;
-esac
-
-case "$action" in
- init | plan | apply | destroy) ;;
- *)
- echo "Usage: $0 {init|plan|apply|destroy}" >&2
- exit 64
- ;;
-esac
-
-script_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
-source_root=$(CDPATH='' cd -- "$script_dir/../.." && pwd)
-example_root="$source_root/examples/migration-test"
-expected_lockfile=".terraform.lock.hcl"
-if [ "$iac_binary" = tofu ]; then
- expected_lockfile="$expected_lockfile.tofu"
-fi
-lockfile_name="${IAC_LOCK_FILE:-$expected_lockfile}"
-if [ "$lockfile_name" != "$expected_lockfile" ]; then
- echo "Lock file does not match IaC binary: $lockfile_name (expected $expected_lockfile)" >&2
- exit 64
-fi
-
-case "$lockfile_name" in
- .terraform.lock.hcl | .terraform.lock.hcl.tofu) ;;
- *)
- echo "Supported lock files are: .terraform.lock.hcl, .terraform.lock.hcl.tofu" >&2
- exit 64
- ;;
-esac
-
-lockfile_backup_dir=""
-lambda_fixture_dir=""
-lambda_created_paths=""
-ami_created_ids=""
-migration_state_backup=""
-migration_iam_policy_v1_snapshot=""
-migration_iam_policy_v2_snapshot=""
-
-lambda_zip_paths="
-$source_root/lambdas/functions/ami-housekeeper/ami-housekeeper.zip
-$source_root/lambdas/functions/control-plane/runners.zip
-$source_root/lambdas/functions/gh-agent-syncer/runner-binaries-syncer.zip
-$source_root/lambdas/functions/webhook/webhook.zip
-$source_root/lambdas/functions/termination-watcher/termination-watcher.zip
-"
-
-ministack_aws() {
- aws --endpoint-url "$AWS_ENDPOINT_URL" --region "$AWS_DEFAULT_REGION" "$@"
-}
-
-wait_for_ministack() {
- attempts=60
- while ! curl -fsS --max-time 2 "$AWS_ENDPOINT_URL/_ministack/health" >/dev/null 2>&1; do
- attempts=$((attempts - 1))
- if [ "$attempts" -le 0 ]; then
- echo "MiniStack did not become ready at $AWS_ENDPOINT_URL." >&2
- exit 70
- fi
- sleep 1
- done
-}
-
-prepare_lockfiles() {
- lockfile_backup_dir=$(mktemp -d "${TMPDIR:-/tmp}/terraform-aws-github-runner-migration-lock.XXXXXX")
- for phase in v1 v2; do
- target="$example_root/$phase/.terraform.lock.hcl"
- source="$example_root/$phase/$lockfile_name"
- if [ "$source" = "$target" ]; then
- continue
- fi
- if [ -f "$target" ]; then
- cp "$target" "$lockfile_backup_dir/$phase.lock"
- : > "$lockfile_backup_dir/$phase.exists"
- fi
- cp "$source" "$target"
- done
-}
-
-restore_lockfiles() {
- if [ -z "$lockfile_backup_dir" ]; then
- return
- fi
- for phase in v1 v2; do
- target="$example_root/$phase/.terraform.lock.hcl"
- source="$example_root/$phase/$lockfile_name"
- if [ "$source" = "$target" ]; then
- continue
- fi
- if [ -f "$lockfile_backup_dir/$phase.exists" ]; then
- cp "$lockfile_backup_dir/$phase.lock" "$target"
- else
- rm -f "$target"
- fi
- done
- rm -rf "$lockfile_backup_dir"
- lockfile_backup_dir=""
-}
-
-create_ami_fixture() {
- ami_name="$1"
- architecture="$2"
- ami_id=$(ministack_aws ec2 describe-images \
- --owners self \
- --filters "Name=name,Values=$ami_name" "Name=state,Values=available" \
- --query 'Images[0].ImageId' \
- --output text)
-
- if [ "$ami_id" = "None" ]; then
- ami_id=$(ministack_aws ec2 register-image \
- --name "$ami_name" \
- --description "MiniStack test-only AMI" \
- --architecture "$architecture" \
- --root-device-name /dev/xvda \
- --virtualization-type hvm \
- --image-location alpine:3.20 \
- --query 'ImageId' \
- --output text)
- ami_created_ids="$ami_created_ids
-$ami_id"
- fi
-}
-
-create_ministack_fixtures() {
- if ! command -v aws >/dev/null 2>&1; then
- echo "AWS CLI is required to seed MiniStack API fixtures." >&2
- exit 69
- fi
- if ! command -v zip >/dev/null 2>&1; then
- echo "zip is required to create Lambda fixture packages." >&2
- exit 69
- fi
- if ! command -v curl >/dev/null 2>&1; then
- echo "curl is required to check MiniStack readiness." >&2
- exit 69
- fi
-
- wait_for_ministack
- lambda_fixture_dir=$(mktemp -d "${TMPDIR:-/tmp}/terraform-aws-github-runner-ministack-lambda.XXXXXX")
- printf '%s\n' 'exports.handler = async () => ({ statusCode: 200, body: "ministack" });' > "$lambda_fixture_dir/index.js"
- (CDPATH='' cd -- "$lambda_fixture_dir" && zip -q ministack-lambda.zip index.js)
-
- for lambda_zip in $lambda_zip_paths; do
- if [ -e "$lambda_zip" ]; then
- continue
- fi
- mkdir -p "$(dirname "$lambda_zip")"
- cp "$lambda_fixture_dir/ministack-lambda.zip" "$lambda_zip"
- lambda_created_paths="$lambda_created_paths
-$lambda_zip"
- done
-
- create_ami_fixture migration-test-linux x86_64 >/dev/null
-}
-
-cleanup() {
- restore_lockfiles
- for image_id in $ami_created_ids; do
- ministack_aws ec2 deregister-image --image-id "$image_id" >/dev/null 2>&1 || true
- done
- for lambda_zip in $lambda_created_paths; do
- rm -f "$lambda_zip"
- done
- if [ -n "$lambda_fixture_dir" ]; then
- rm -rf "$lambda_fixture_dir"
- fi
- if [ -n "$migration_state_backup" ]; then
- rm -f "$migration_state_backup"
- fi
- if [ -n "$migration_iam_policy_v1_snapshot" ]; then
- rm -f "$migration_iam_policy_v1_snapshot"
- fi
- if [ -n "$migration_iam_policy_v2_snapshot" ]; then
- rm -f "$migration_iam_policy_v2_snapshot"
- fi
-}
-
-iac_migration_init() {
- prepare_lockfiles
- "$iac_binary" -chdir="$example_root/v1" init -reconfigure -input=false
- "$iac_binary" -chdir="$example_root/v2" init -reconfigure -input=false
-}
-
-iac_migration_example() {
- phase="$1"
- shift
- phase_root="$example_root/$phase"
- "$iac_binary" -chdir="$phase_root" "$@" -var-file="$phase_root/$phase.tfvars"
-}
-
-snapshot_migration_iam_policies() {
- python3 "$example_root/compare_iam_role_policies.py" snapshot "$1"
-}
-
-compare_migration_iam_policies() {
- python3 "$example_root/compare_iam_role_policies.py" compare "$1" "$2"
-}
-
-assert_migration_plan_has_no_infrastructure_changes() {
- phase="$1"
- phase_root="$example_root/$phase"
- plan_file=$(mktemp "${TMPDIR:-/tmp}/migration-test-plan.XXXXXX")
- plan_status=0
- if iac_migration_example "$phase" plan -input=false -out="$plan_file"; then
- plan_status=0
- else
- plan_status=$?
- fi
- if [ "$plan_status" -ne 0 ] && [ "$plan_status" -ne 2 ]; then
- rm -f "$plan_file"
- return "$plan_status"
- fi
-
- if "$iac_binary" -chdir="$phase_root" show -json "$plan_file" |
- python3 "$example_root/filter_migration_plan.py"; then
- rm -f "$plan_file"
- return 0
- else
- plan_status=$?
- rm -f "$plan_file"
- return "$plan_status"
- fi
-}
-
-assert_migration_plan_is_empty() {
- assert_migration_plan_has_no_infrastructure_changes "$1"
-}
-
-run_migration_test() {
- iac_migration_init
- iac_migration_example v1 apply -auto-approve -input=false
-
- migration_iam_policy_v1_snapshot=$(mktemp "${TMPDIR:-/tmp}/migration-test-iam-v1.XXXXXX")
- snapshot_migration_iam_policies "$migration_iam_policy_v1_snapshot"
-
- migration_state_backup=$(mktemp "${TMPDIR:-/tmp}/migration-test-state.XXXXXX")
- rm -f "$migration_state_backup"
- python3 "$source_root/scripts/migrate_multi_runner_state.py" \
- --working-directory "$example_root/v1" \
- --tool "$iac_binary" \
- --backup "$migration_state_backup" \
- --apply \
- --yes
-
- assert_migration_plan_has_no_infrastructure_changes v2
- iac_migration_example v2 apply -auto-approve -input=false
-
- migration_iam_policy_v2_snapshot=$(mktemp "${TMPDIR:-/tmp}/migration-test-iam-v2.XXXXXX")
- snapshot_migration_iam_policies "$migration_iam_policy_v2_snapshot"
- compare_migration_iam_policies "$migration_iam_policy_v1_snapshot" "$migration_iam_policy_v2_snapshot"
-
- assert_migration_plan_is_empty v2
-}
-
-trap cleanup EXIT INT TERM
-
-case "$action" in
- init)
- iac_migration_init
- ;;
- plan)
- create_ministack_fixtures
- iac_migration_init
- iac_migration_example v1 plan -input=false
- ;;
- apply)
- create_ministack_fixtures
- run_migration_test
- ;;
- destroy)
- create_ministack_fixtures
- iac_migration_init
- iac_migration_example v2 destroy -auto-approve -input=false
- ;;
-esac
diff --git a/versions.tf b/versions.tf
index 224f3e7c76..d60d1772ff 100644
--- a/versions.tf
+++ b/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 1.5.6"
+ required_version = ">= 1.3.0"
required_providers {
aws = {