Skip to content

Commit 210fec2

Browse files
docs(compute-providers): document EC2 provider boundary
1 parent deaf3d5 commit 210fec2

6 files changed

Lines changed: 83 additions & 30 deletions

File tree

docs/modules/internal/compute-provider-refactor.md

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
The scale-up, scale-down, pool, job-retry, queue, SSM housekeeping, and GitHub registration workflows are not inherently EC2-specific. The legacy `runners` module combines that common control plane with EC2 launch templates, instance profiles, bootstrap parameters, log groups, IAM permissions, and Lambda environment variables. Adding another compute provider in that structure would require copying common behavior or adding provider conditionals throughout the module.
1010

11-
The refactor introduces a provider boundary so a future microVM or other backend can reuse the control plane. Only the policy statements, environment variables, and resources required by the selected compute provider should change.
11+
The refactor introduces a provider boundary so a future MicroVM or other backend can reuse the control plane. Only the policy statements, environment variables, and resources required by the selected compute provider should change.
1212

1313
## Ownership model
1414

@@ -22,24 +22,27 @@ The implementation is split into orchestration, provider-neutral control-plane c
2222
| `runner-stack/pool` | Optional scheduled runner-pool resources and their Lambda and IAM wiring. |
2323
| `runner-stack/job-retry` | Optional queued-job retry resources and their Lambda and IAM wiring. |
2424
| `runner-stack/ssm-housekeeper` | Parameter Store cleanup Lambda, schedule, logging, and IAM resources. |
25-
| `compute-providers/<provider>` | Provider-specific resources, runner-role policy requirements, and the IAM and environment-variable fragments consumed by the common control plane. |
25+
| `compute-providers/<provider>/trust-policy` | Provider-specific default runner-role trust, merged with the optional caller-provided trust document before the common role is created. |
26+
| `compute-providers/<provider>` | Provider-specific resources, permission requirements, and the IAM and environment-variable fragments consumed by the common control plane after the runner role is resolved. |
2627

27-
The EC2 provider currently owns the instance profile, launch template, security group, AMI and bootstrap parameters, runner log groups, EC2 policy statements, and EC2 Lambda environment variables. EC2 is the only implemented Terraform compute provider today.
28+
The EC2 provider owns the instance profile, launch template, security group, AMI and bootstrap parameters, runner log groups, EC2 policy statements, and EC2 Lambda environment variables. EC2 is the only implemented Terraform compute provider in this phase.
2829

2930
The modules below `runner-stack` are internal implementation boundaries, not standalone public modules. Callers opt into the experimental interface through `experimental.multi_runner_config_v2`; `multi-runner` calls `runner-stack`, which composes the internal modules. Their direct input and output contracts may change while v2 remains experimental.
3031

31-
`runner-stack` selects a compute provider from the single populated typed block under `compute_provider`. For example, `compute_provider = { ec2 = { ... } }` selects EC2; there is no separate `type` input that can disagree with the populated block. Exactly one provider block must be populated, and its presence must be known during planning because it determines the module graph. The stack passes `compute_provider.ec2` to the EC2 module as one nested `config` object. It also passes the provider-neutral `runner`, `github`, `ssm`, and `observability` objects without expanding them back into prefixed scalar inputs. This keeps ownership visible at the module boundary and gives future compute providers an equivalent contract to implement.
32+
`runner-stack` selects a compute provider from the single populated typed block under `compute_provider`. For example, `compute_provider = { ec2 = { ... } }` selects EC2; there is no separate `type` input that can disagree with the populated block. Exactly one provider block must be populated, and its presence must be known during planning because it determines the module graph. Native input validation enforces this common selection rule, while each compute-provider module owns its provider-specific semantic validation. The stack passes `compute_provider.<provider>` to the selected provider module as one nested `config` object. It also passes the provider-neutral `runner`, `github`, `ssm`, and `observability` objects without expanding them back into prefixed scalar inputs. This keeps ownership visible at the module boundary and gives future compute providers an equivalent contract to implement.
3233

33-
The common stack creates or selects the runner IAM role and owns the role trust relationship. The selected provider returns a single nested contract containing `policies.runner`, `policies.scale_up`, `policies.scale_down`, and `policies.pool`, along with component environment variables and provider resources. The common stack attaches those permission documents to the roles owned by the corresponding common components. A provider never creates or attaches a common IAM role.
34+
The common stack creates or selects the runner IAM role, but the selected provider owns the role's default trust-policy document. Each provider implements a small `trust-policy` submodule that accepts `additional_trust_policy_json` and returns the final `assume_role_policy`. The full provider separately returns its nested `provider` contract containing `policies.runner`, `policies.scale_up`, `policies.scale_down`, and `policies.pool`, component environment variables, and provider resources. The common stack uses the isolated trust-policy output when it creates the runner role and attaches the full provider's permission documents to the roles owned by the corresponding common components. A provider never creates or attaches a common IAM role.
3435

35-
The trust relationship is deliberately resolved before the provider is called:
36+
The trust relationship is deliberately rendered by an isolated provider submodule:
3637

37-
1. `runner-stack` creates or selects the runner role using the service principal associated with the populated provider block.
38-
2. The compute provider receives that role so it can create resources such as the EC2 instance profile and render `iam:PassRole` statements.
39-
3. The provider returns its nested policy and environment-variable contract.
40-
4. The common components attach the returned policies to the runner, scale-up, scale-down, and pool roles they own.
38+
1. `runner-stack` selects the provider from the populated typed block.
39+
2. `compute-providers/<provider>/trust-policy` combines the provider default with `runner.iam.additional_trust_policy_json` without referencing the runner-role input.
40+
3. `runner-stack` creates or selects the common runner role from the returned `assume_role_policy`.
41+
4. The full compute provider receives the resolved role so it can create resources such as the EC2 instance profile and render `iam:PassRole` statements.
42+
5. The provider returns its nested policy, environment-variable, and resource contract.
43+
6. The common components attach the returned policies to the runner, scale-up, scale-down, and pool roles they own.
4144

42-
Returning the runner trust policy from the same resource-bearing provider module would create a Terraform dependency cycle: the role would depend on the provider output while the provider already depends on the role input. Keeping trust establishment in `runner-stack` and attaching provider permissions afterward preserves a one-way graph.
45+
The trust-policy output depends only on its input documents, not on the full provider resources that consume the runner role. This preserves provider ownership of the trust relationship while keeping the dependency graph one-way.
4346

4447
## Phase 1 dispatch and compatibility
4548

@@ -59,7 +62,10 @@ flowchart TD
5962
Stack --> Pool["runner-stack/pool"]
6063
Stack --> Retry["runner-stack/job-retry"]
6164
Stack --> Housekeeper["runner-stack/ssm-housekeeper"]
62-
Stack --> Provider["compute-providers/ec2"]
65+
Stack --> Trust["compute-providers/provider/trust-policy"]
66+
Trust --> Role["common runner role"]
67+
Role --> Provider
68+
Stack --> Provider["compute-providers/<provider>"]
6369
Provider --> Scaling
6470
Provider --> Pool
6571
```
@@ -117,7 +123,7 @@ Tags follow the same ownership model. Module tags are defaults; shared Lambda, q
117123

118124
Application logging settings stay together under `observability.logs`, including `level`, retention, encryption, class, and shared log-group tags.
119125

120-
In v1 mode, entries remain exclusively in `runners_map` and retain their flat output fields; `runners_map_v2` is empty. In v2 mode, entries are exposed exclusively through `runners_map_v2` and `runners_map` is empty. Common resources are grouped under `runner`, `scale_up`, `scale_down`, and `pool`, while provider-specific resources remain under `provider.<provider>`. For example, the common runner role is available at `runners_map_v2["configuration"].runner.role`, while EC2 launch-template and runner-log artifacts are under `runners_map_v2["configuration"].provider.ec2`. The populated provider key identifies the compute provider without a duplicate type field. The `pool` value is null when no pool configuration is supplied.
126+
In v1 mode, entries remain exclusively in `runners_map` and retain their flat output fields; `runners_map_v2` is empty. In v2 mode, entries are exposed exclusively through `runners_map_v2` and `runners_map` is empty. Common resources are grouped under `runner`, `scale_up`, `scale_down`, and `pool`, while provider-specific resources remain under `provider.<provider>`. The provider key is derived dynamically from the selected input block and therefore also identifies the compute provider. For example, the common runner role is available at `runners_map_v2["configuration"].runner.role`, while an EC2 selection places launch-template and runner-log artifacts under `runners_map_v2["configuration"].provider.ec2`. The `pool` value is null when no pool configuration is supplied.
121127

122128
## Plan-time provider selection and ownership wrappers
123129

0 commit comments

Comments
 (0)