Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
2abddcd
feat(multi-runner): add experimental lane config v2
edersonbrilhante Aug 5, 2026
e5fa56f
docs: auto update terraform docs
github-actions[bot] Aug 5, 2026
61a6f0e
refactor(multi-runner): decouple EC2 Terraform logic into provider mo…
edersonbrilhante Aug 7, 2026
c9d018e
refactor(multi-runner): preserve legacy runner path
edersonbrilhante Aug 7, 2026
5b8d2c5
refactor(multi-runner): isolate v1 and v2 provider routing
edersonbrilhante Aug 7, 2026
68eb801
refactor(runner-stack): key provider output dynamically
edersonbrilhante Aug 7, 2026
4dfc151
refactor(compute-providers): isolate EC2 runner contracts
edersonbrilhante Aug 13, 2026
4735d85
docs(compute-providers): document EC2 provider boundary
edersonbrilhante Aug 13, 2026
be6270f
fix(runner-stack): preserve multi-app credentials
edersonbrilhante Aug 14, 2026
519d1d8
fix(multi-runner): keep binary syncer keys plan-known
edersonbrilhante Aug 14, 2026
9bcca0d
docs: auto update terraform docs
github-actions[bot] Aug 14, 2026
e467642
feat(termination-watcher): support encrypted app parameters
edersonbrilhante Aug 15, 2026
6b029e6
feat(multi-runner): add canonical experimental configuration
edersonbrilhante Aug 15, 2026
d354272
docs(multi-runner): document experimental configuration
edersonbrilhante Aug 15, 2026
50ecc05
docs: auto update terraform docs
github-actions[bot] Aug 15, 2026
f7e0ed7
docs(multi-runner): restore stable input descriptions
edersonbrilhante Aug 15, 2026
704fc6e
refactor(multi-runner): isolate termination watcher fix
edersonbrilhante Aug 15, 2026
cbb6688
refactor(multi-runner): nest GitHub client settings
edersonbrilhante Aug 15, 2026
30ab11b
docs(multi-runner): align experimental GitHub paths
edersonbrilhante Aug 15, 2026
da77c13
fix(multi-runner): allow v2 to omit flat inputs
edersonbrilhante Aug 15, 2026
d524db1
docs(multi-runner): document optional flat inputs
edersonbrilhante Aug 15, 2026
e9f89f7
refactor(multi-runner): add orchestration provider boundary
edersonbrilhante Aug 15, 2026
c7dd66a
docs: auto update terraform docs
github-actions[bot] Aug 15, 2026
8bfb2ec
refactor(multi-runner): group webhook-owned settings
edersonbrilhante Aug 15, 2026
7b33278
docs: auto update terraform docs
github-actions[bot] Aug 15, 2026
e621653
refactor(runner-config): clarify provider module addresses
edersonbrilhante Aug 15, 2026
6ff0f84
docs(orchestration): document provider configuration
edersonbrilhante Aug 15, 2026
2811343
refactor(state): remove legacy module moves
edersonbrilhante Aug 15, 2026
4063043
refactor(orchestration): align provider boundaries
edersonbrilhante Aug 15, 2026
5e07ae7
fix(multi-runner): preserve queue policy behavior
edersonbrilhante Aug 15, 2026
18ddc3a
docs(adr): remove warm-pool references
edersonbrilhante Aug 15, 2026
a4a7792
docs(orchestration): align provider boundary guidance
edersonbrilhante Aug 15, 2026
6e9433f
feat(compute-providers): namespace AWS EC2 provider
edersonbrilhante Aug 17, 2026
5df0167
docs: auto update terraform docs
github-actions[bot] Aug 17, 2026
83d25b9
fix(multi-runner): keep provider topology plan-known
edersonbrilhante Aug 17, 2026
127c8f8
docs: auto update terraform docs
github-actions[bot] Aug 17, 2026
e8a3cfb
refactor(storage): extract runner config store
edersonbrilhante Aug 18, 2026
90c0471
refactor(storage): extract group cache and cleanup
edersonbrilhante Aug 18, 2026
ddaeedc
refactor(storage): move local housekeeper harness
edersonbrilhante Aug 18, 2026
77f78e6
refactor(storage): preserve SSM cleanup names
edersonbrilhante Aug 18, 2026
0ac623d
test(storage): decouple scale-up tests from SSM
edersonbrilhante Aug 18, 2026
4726813
refactor(storage): extract GitHub App credentials
edersonbrilhante Aug 19, 2026
54403da
refactor(storage): extract webhook matcher config
edersonbrilhante Aug 19, 2026
0150fb2
refactor(storage): extract webhook secret store
edersonbrilhante Aug 19, 2026
dc99f28
feat(storage): add DynamoDB provider
edersonbrilhante Aug 19, 2026
f95eec4
feat(storage): track shared runner state in DynamoDB
edersonbrilhante Aug 19, 2026
ab5613c
feat(terraform): wire shared DynamoDB storage
edersonbrilhante Aug 19, 2026
8d54e5a
docs: auto update terraform docs
github-actions[bot] Aug 19, 2026
0563c7c
fix(terraform): support Terraform 1.5 validation
edersonbrilhante Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/terraform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,16 @@ jobs:
"download-lambda",
"lambda",
"multi-runner",
"compute-providers/aws/ec2",
"compute-providers/aws/ec2/trust-policy",
"runner-binaries-syncer",
"storage-providers/aws/dynamodb",
"orchestration-providers/webhook",
"orchestration-providers/webhook/job-retry",
"orchestration-providers/webhook/pool",
"orchestration-providers/webhook/scale-runners",
"runner-config",
"runner-config/ssm-housekeeper",
"runners",
"setup-iam-permissions",
"ssm",
Expand Down Expand Up @@ -214,6 +223,16 @@ jobs:
matrix:
module:
- modules/runners
- modules/multi-runner
- modules/orchestration-providers/webhook
- modules/orchestration-providers/webhook/job-retry
- modules/orchestration-providers/webhook/pool
- modules/orchestration-providers/webhook/scale-runners
- modules/runner-config
- modules/runner-config/ssm-housekeeper
- modules/storage-providers/aws/dynamodb
- modules/compute-providers/aws/ec2
- modules/compute-providers/aws/ec2/trust-policy
defaults:
run:
working-directory: ${{ matrix.module }}
Expand Down
300 changes: 300 additions & 0 deletions docs/adr/002-runner-orchestration-provider-boundary.md

Large diffs are not rendered by default.

18 changes: 17 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,23 @@ Besides these permissions, the lambdas also need permission to CloudWatch (for l

## Terraform main modules

Currently we support two main modules. The `runners` module is the main module for creating runners. And the 'multi-runner' module is a wrapper around the `runners` module to create multiple runners in one go. The `multi-runner` module is useful for creating runners for multiple repositories or organizations.
Currently we support two main modules. The existing `runners` module remains the stable EC2 implementation, and the `multi-runner` module creates multiple runner configurations in one deployment. Stable top-level `multi_runner_config` entries continue to use the unchanged `runners` module when `experimental.multi_runner_config` is empty. A non-empty experimental map takes priority over the stable map; the maps are not combined. Experimental entries use the new provider-oriented `runner-config`.

Multi-runner centralizes mode selection and canonical configuration in `config.experimental.translation.tf`. A non-empty experimental runner-configuration map selects v2; otherwise the file projects the flat globals and stable runner configurations into the same schema. That selection produces `local.raw_translated_experimental`, from which the same file derives `local.translated_experimental_base` by applying schema defaults, global/runner-configuration precedence, tag merges, IAM ownership, paths, observability, webhook queues, and provider defaults. Provider selection and the shared runner-binary syncer and discovery use this plan-known base. After discovery, the translation file derives the final `local.translated_experimental`, including labels, runner-config GitHub client settings, webhook queue event mapping, Lambda artifact and principals, the webhook pool Lambda wrapper, SSM KMS, and the discovered EC2 binaries object. The remaining shared components, webhook queues, and runner implementations consume that final canonical representation. Stable runner configurations are adapted back into the existing `module.runners["configuration"]` call, preserving their Terraform addresses while removing a second configuration path. The `module.runner_configs` call directly iterates the gated final runner configurations, inlines the environment tag and live GitHub App and build-queue references into `orchestration_provider.webhook`, and forwards the remaining canonical objects, including the typed orchestration and compute-provider wrappers.

The `experimental` object provides sibling global defaults through `tags`, `roles`, `runner`, `github`, `lambda`, `orchestration_provider`, `ssm`, `observability`, and `compute_provider`. Root `experimental.lambda` is provider-neutral shared Lambda substrate: the artifact bucket, runtime, architecture, principals, networking, tags, and role defaults used across orchestration and non-orchestration consumers. Webhook-specific global defaults live together under `experimental.orchestration_provider.webhook`, including the maximum runner count, the shared webhook's routing and matcher storage, build-queue defaults and encryption, control-plane artifact selectors, and webhook, scale-up, scale-down, and pool Lambda settings. This global block supplies defaults; it does not select an orchestration provider. Each runner configuration separately makes that selection through its own `orchestration_provider` wrapper. The only supported orchestration provider today is `orchestration_provider.webhook`, which owns that runner configuration's maximum runner count, registration scope, matcher, build-queue overrides, scale-up and scale-down settings, scheduled pool, and job retry. Keeping those fields behind a typed provider wrapper allows future orchestration providers to be introduced as mutually exclusive siblings without moving the common runner, Lambda substrate, SSM, observability, or compute-provider contracts again. The shared GitHub App Parameter Store module, webhook, runner-binary syncer, termination watcher, and AMI housekeeper consume the translated global values. Migrated v2 consumers do not fall back to matching flat inputs; those flat values seed stable-mode translation only. Per-configuration overrides remain configuration-specific. A nullable runner-configuration field with a corresponding experimental global inherits that global value when omitted or null. A runner configuration that selects an external runner IAM role intentionally suppresses inherited managed policies and additional trust policy JSON because the module does not manage that role. Tag maps merge from broad to narrow. Only module naming (`prefix`), `aws_partition`, and `aws_region` remain active flat-only inputs; legacy `iam_overrides` remains in the input schema but has no active consumer.

Global `experimental.orchestration_provider.webhook.queue` owns v2 defaults for build-queue delay (`30` seconds), retention (`86400` seconds), visibility (`180` seconds), redrive, tags, and encryption. Runner-configuration `experimental.multi_runner_config[].orchestration_provider.webhook.queue` fields override the global delay, retention, visibility, redrive, and tag values; encryption remains global-only. Omitting the whole encryption block selects SQS-managed encryption and null KMS attributes. If the block is supplied explicitly, all three leaf keys are required: use a non-null `sqs_managed_sse_enabled` with null KMS fields for the non-KMS mode, or set that field to null and provide `kms_master_key_id` for KMS mode. This encryption configures the multi-runner build queues and their dead-letter queues, not the webhook provider's separate job-retry queue, and its CMK is independent from `experimental.ssm.kms_key_id`. Runner-config forwards the distinct build-queue key to the webhook orchestration provider: scale-up receives `kms:Decrypt`, while job-retry receives `kms:Decrypt` and `kms:GenerateDataKey` for publishing. The existing shared `modules/webhook` contract remains unchanged and still requires caller-supplied key access when that publisher targets customer-managed encrypted queues. For v2, `experimental.multi_runner_config[].orchestration_provider.webhook.queue.visibility_timeout_seconds` must be at least six times the resolved `experimental.multi_runner_config[].orchestration_provider.webhook.lambda.scale.up.timeout`; the Lambda timeout does not itself configure queue visibility. The v1 translation continues to use `runners_scale_up_lambda_timeout` and flat `queue_encryption`.

V2 requires `experimental.github.app`, and `experimental.github.additional_apps` defaults to an empty list. These nested values are authoritative end-to-end: the shared Parameter Store module persists or selects their credentials, and v2 runner configurations consume the resulting references. Flat `github_app` and `additional_github_apps` seed stable-mode translation only. `experimental.orchestration_provider.webhook.github.repository_white_list` defaults to `[]` and filters the shared webhook when populated. `experimental.github.enterprise_server.url` defaults to `null` and configures v2 runner-config GitHub clients and the termination watcher. `experimental.github.enterprise_server.ssl_verify` and `experimental.github.user_agent` remain runner-config client settings and default to `true` and `github-aws-runners`. Both client settings belong inside `experimental.github`; they are not root `experimental` fields or per-configuration orchestration settings.

The shared webhook, runner configurations, SSM housekeepers, runner-binary syncer, termination watcher, and AMI housekeeper consume the provider-neutral runtime, architecture, networking, role, and tag defaults under `experimental.lambda`; `lambda.principals` configures runner-config, runner-binary-syncer, termination-watcher, and AMI-housekeeper roles, but not the webhook role. Global observability settings configure logging and tracing, and global metrics also configure the termination watcher. The runner-control artifact shared by webhook scale, pool, and job-retry is selected globally under `experimental.orchestration_provider.webhook.lambda.artifact`: `artifact.zip` selects a local archive, while `artifact.s3.{key,object_version}` selects an object from the shared `experimental.lambda.artifact.s3.bucket`. Leaving both artifact sources null uses the packaged runner archive. The module validates that zip and S3 are not selected together and that an S3 wrapper has a non-null shared bucket and key. Stable-mode translation preserves the legacy precedence in which a configured flat S3 bucket wins over the flat runner zip. The shared bucket alone selects no component. Each artifact-capable singleton—including the webhook—uses it only when that component's separate nested `artifact.s3` wrapper supplies its key and optional object version. Runner-config's common SSM housekeeper independently resolves `multi_runner_config[].ssm.housekeeper.lambda.artifact` over the global `experimental.ssm.housekeeper.lambda.artifact`; S3 combines the component key and version with the shared artifact bucket, zip uses the selected local path, and no selection uses the packaged control-plane archive. The runner-binary syncer follows the same parallel selector at `experimental.compute_provider.aws.ec2.runner_binaries.syncer.artifact.{zip,s3}`, with its S3 key and optional object version also resolved against `experimental.lambda.artifact.s3.bucket`. `experimental.orchestration_provider.webhook` owns queue selection, EventBridge routing, accepted event types, and matcher-parameter tier, while `experimental.orchestration_provider.webhook.lambda.webhook` owns the separate ingress webhook artifact, API Gateway access logs, sizing, and component tags. `compute_provider.aws.ec2.instance_termination_watcher`, `compute_provider.aws.ec2.ami.housekeeper`, and `compute_provider.aws.ec2.runner_binaries` own their singleton-specific features, artifacts, sizing, schedules, and related settings.

`experimental.compute_provider.aws.ec2.runner_binaries.enabled` defaults each EC2 runner configuration to the shared synchronized distribution, while a nullable per-configuration `compute_provider.aws.ec2.binaries_syncer.enabled` can override it. Enabled distributions are created once per unique operating-system and architecture pair. The global enable value, distribution encryption enablement, distribution KMS-key nullness, and access-logging bucket nullness must be known during planning because they determine module or resource shape. A distribution-bucket CMK grants the syncer access, but runner roles do not derive `kms:Decrypt` from that setting; attach decrypt permission to module-managed or external runner roles separately.

Global `ssm.paths.root` is the base for shared and runner-configuration-owned parameters. The shared GitHub App and webhook paths append `ssm.paths.app` (default `app`) and `ssm.paths.webhook` (default `webhook`), while normalization appends the runner-configuration key only for configuration-owned paths. The default derived base is `/github-action-runners/${prefix}`, and runner token/config segments default to `runners/tokens` and `runners/config`. Global `ssm.kms_key_id` is an optional ARN-valued scalar that encrypts the shared GitHub App parameters, configures the webhook, and adds matching decrypt permissions to every runner configuration; it does not select encryption for runtime-created runner parameters. Webhook-provider leaves conditionally omit their KMS statements when this value is null, while apply-time-unknown key ARNs remain valid during planning. Nested metrics retain the established defaults: disabled, using the `GitHub Runners` namespace, with the rate-limit, job-retry, Spot-termination, and Spot-warning switches enabled. Spot metrics are global termination-watcher settings rather than per-configuration overrides.

Each runner configuration selects two independent typed providers: one `orchestration_provider` provider for demand control and one namespaced `compute_provider` for runner capacity. `orchestration_provider.webhook` is the sole supported orchestration provider today. The global `experimental.compute_provider` block owns shared v2 provider defaults plus the runner-binary, termination-watcher, and AMI-housekeeper singleton configuration, but it does not select a provider. Today the only selectable compute leaf is `compute_provider.aws.ec2`. The wrapped provider objects reach runner-config, which validates each exact-one selection and dispatches the matching root-level provider module. `orchestration-providers/webhook` owns scale-up, scale-down, scheduled pool, job retry, and their webhook-specific defaults and tag layering; runner-config retains common SSM housekeeping plus the common runner role and attachments. The EC2 implementation lives under `compute-providers/aws/ec2`, supplies EC2-specific policy requirements, and owns the instance profile, launch template, bootstrap resources, and runner log groups. Runner-config dispatches it at `module.compute_aws_ec2[0]` and exposes its resources under the matching nested output path `provider.aws.ec2` (for multi-runner, `runners_map_v2["<runner_config>"].provider.aws.ec2`). Declarative moved blocks preserve state created at the earlier experimental `module.compute_ec2[0]` and `module.compute_ec2_trust_policy[0]` child addresses when upgrading to the namespaced labels. They do not migrate stable-v1 `module.runners` state to v2, and they cannot rewrite configuration references from `provider.ec2` to `provider.aws.ec2`. These modules are internal experimental implementation boundaries, not standalone public entry points. Later releases can add mutually exclusive namespace and provider siblings without changing the common contract. See the [experimental orchestration- and compute-provider refactor](modules/internal/compute-provider-refactor.md) and [multi-runner v2 migration roadmap](modules/public/multi-runner.md#multi-runner-v2-migration-roadmap). EC2 is the only active Terraform-managed provider; microVM, CodeBuild, and other provider modules are future work.

Both modules are built on top of the same base modules. When using the multi-runner module you can deploy different runners with only one deployment.

Expand Down
Loading