Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ad1598c
feat(compute-providers): add Lambda MicroVM Terraform provider
edersonbrilhante Sep 3, 2026
051443c
fix(ci): correct MicroVM module matrix paths
edersonbrilhante Sep 11, 2026
510e093
Update terraform.yml
edersonbrilhante Sep 17, 2026
d91583d
Merge branch 'main' into microvm-provider-independent
edersonbrilhante Sep 17, 2026
ff90c06
feat(microvm): wire MicroVM provider in multi-runner module (#5363)
edersonbrilhante Sep 18, 2026
f1239ba
chore: remove useless changes
edersonbrilhante Sep 18, 2026
cd0d498
chore: remove unless changes in README
edersonbrilhante Sep 18, 2026
f4338cc
ci: add microvm in terraform pipeline
edersonbrilhante Sep 18, 2026
41afd47
test: fix fixtures
edersonbrilhante Sep 18, 2026
d7f2afd
fix: fix terraform tests
edersonbrilhante Sep 18, 2026
62f41ac
docs: auto update terraform docs
github-actions[bot] Sep 18, 2026
571ec5a
Merge branch 'main' into microvm-provider-independent
edersonbrilhante Sep 21, 2026
9972dbc
docs: auto update terraform docs
github-actions[bot] Sep 21, 2026
f69f0e8
Merge branch 'main' into microvm-provider-independent
edersonbrilhante Sep 22, 2026
bf50446
docs: auto update terraform docs
github-actions[bot] Sep 22, 2026
f85c2bd
fix: fix conflicts
edersonbrilhante Sep 22, 2026
1d7f951
docs: auto update terraform docs
github-actions[bot] Sep 22, 2026
bb3fedb
fix: fix conflicts
edersonbrilhante Sep 22, 2026
e7fd0ae
docs: auto update terraform docs
github-actions[bot] Sep 22, 2026
730e8d7
fix: fix conflicts with ssm
edersonbrilhante Sep 22, 2026
95c9a2a
fix: fix conflicts with merge
edersonbrilhante Sep 22, 2026
bd10a1f
Merge branch 'main' into microvm-provider-independent
edersonbrilhante Sep 23, 2026
a49f954
Merge branch 'main' into microvm-provider-independent
edersonbrilhante Sep 23, 2026
cce87ae
docs: auto update terraform docs
github-actions[bot] Sep 23, 2026
5f43746
Merge remote-tracking branch 'origin/main' into microvm-provider-inde…
edersonbrilhante Sep 25, 2026
8174cb6
feat: move code from feat-microvm-lifecycle-hooks
edersonbrilhante Sep 25, 2026
831b9d6
fix: fix conflicts
edersonbrilhante Sep 25, 2026
4ebc82e
Merge remote-tracking branch 'origin/main' into microvm-provider-inde…
edersonbrilhante Sep 25, 2026
fbbbb28
docs: auto update terraform docs
github-actions[bot] Sep 25, 2026
e18b6a9
test: add missing providers lock
edersonbrilhante Sep 25, 2026
8e62966
Merge branch 'main' into microvm-provider-independent
edersonbrilhante Sep 25, 2026
0866ba4
docs: auto update terraform docs
github-actions[bot] Sep 25, 2026
b652ad8
Merge branch 'main' into microvm-provider-independent
edersonbrilhante Sep 26, 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
4 changes: 4 additions & 0 deletions .github/workflows/terraform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ env:
modules/ami-housekeeper
modules/compute-providers/aws/ec2
modules/compute-providers/aws/ec2/trust-policy
modules/compute-providers/aws/microvm
modules/compute-providers/aws/microvm/trust-policy
modules/download-lambda
modules/lambda
modules/multi-runner
Expand Down Expand Up @@ -56,6 +58,8 @@ env:
TEST_MODULES: |
modules/runners
modules/multi-runner
modules/compute-providers/aws/microvm
modules/compute-providers/aws/microvm/trust-policy
modules/orchestration-providers/scale-set

jobs:
Expand Down
20 changes: 18 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.96.2
rev: 581213484f4262600d17c71e272176d4eb6724a5 # frozen: v1.109.0
hooks:
- id: terraform_fmt
name: Terraform · Format
- id: terraform_tflint
name: Terraform · TFLint
args:
- --args=--config=__GIT_WORKING_DIR__/.tflint.hcl --var-file __GIT_WORKING_DIR__/.github/lint/tflint.tfvars
- id: terraform_validate
name: Terraform · Validate
args:
- --hook-config=--retry-once-with-cleanup=true
- --tf-init-args=-backend=false
- --tf-init-args=--upgrade=false
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
rev: 3e8a8703264a2f4a69428a0aa4dcb512790b2c8c # frozen: v6.0.0
hooks:
- id: check-merge-conflict
name: Git · Check merge conflict
always_run: true
- id: check-case-conflict
name: Git · Case conflict
always_run: true
- id: forbid-new-submodules
name: Git · Forbid new submodules
always_run: true
69 changes: 69 additions & 0 deletions modules/compute-providers/aws/microvm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# AWS Lambda MicroVM runner provider

This internal module implements the AWS Lambda MicroVM compute provider used by `runner-config`. It returns provider-specific Lambda environment variables, control-plane IAM policy fragments, selected image metadata, native runtime and optional CloudWatch-agent log groups, and collected-file definitions through the common provider contract; the parent owns the runner role, Lambda resources, queues, schedules, and Parameter Store lifecycle.

Select it with the `compute_provider.aws.microvm` leaf. The Terraform dispatch key is `aws_microvm`, while the runtime `COMPUTE_PROVIDER_TYPE` remains `microvm` for compatibility with the control-plane Lambda. MicroVM lanes require Linux on ARM64 and ephemeral webhook orchestration with just-in-time configuration enabled.

MicroVM runners use the provider's fixed 28,800-second (8-hour) lifetime; this is not a Terraform input.

The resolved provider-neutral `runner.iam.role` is passed to Lambda as the MicroVM execution role. The provider creates `/github-self-hosted-runners/<prefix>/microvm` with the common observability lifecycle and derives a metadata prefix at `<ssm.paths.root>/<ssm.paths.config>/microvm-metadata`. Scale-up, scale-down, and pool use that non-secret prefix for MicroVM ownership and lifecycle state; the runner role can read only the lane's `*.tags` metadata records, the CloudWatch enablement parameter, and its lane-scoped one-time JIT parameter. It can also delete that JIT parameter and write to the provider-managed log groups. MicroVMs sharing the execution role can read the tag records for that lane, but not the ownership and cleanup records. When the runner role is supplied externally, its Lambda trust and these permissions remain caller-owned.

<!-- BEGIN_TF_DOCS -->
## Requirements

| Name | Version |
|------|---------|
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | >= 1.4.0 |
| <a name="requirement_aws"></a> [aws](#requirement\_aws) | >= 6.33 |

## Providers

| Name | Version |
|------|---------|
| <a name="provider_aws"></a> [aws](#provider\_aws) | >= 6.33 |
| <a name="provider_terraform"></a> [terraform](#provider\_terraform) | n/a |

## Modules

No modules.

## Resources

| Name | Type |
|------|------|
| [aws_cloudwatch_log_group.gh_runners](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
| [aws_cloudwatch_log_group.runtime](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group) | resource |
| [aws_ssm_parameter.cloudwatch_agent_config_runner](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
| [aws_ssm_parameter.runner_enable_cloudwatch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ssm_parameter) | resource |
| [terraform_data.validate_config](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource |
| [terraform_data.validate_runner](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource |
| [aws_caller_identity.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source |
| [aws_iam_policy_document.runner_cloudwatch](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.runner_metadata](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.runner_runtime_logs](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.runner_ssm_jit](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.scale_down](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |
| [aws_iam_policy_document.scale_up](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_aws_partition"></a> [aws\_partition](#input\_aws\_partition) | AWS partition used to construct IAM ARNs. | `string` | `"aws"` | no |
| <a name="input_aws_region"></a> [aws\_region](#input\_aws\_region) | AWS region used by compute-provider resources and policy documents. | `string` | n/a | yes |
| <a name="input_config"></a> [config](#input\_config) | Lambda MicroVM compute-provider configuration. Paths match `compute_provider.aws.microvm` in runner-config.<br/><br/>- `image_arn`: ARN of the MicroVM image used to run GitHub runners.<br/>- `image_version`: Optional MicroVM image version.<br/>- `ingress_network_connectors`: Up to 10 Lambda network-connector ARNs passed to RunMicrovm.<br/>- `egress_network_connectors`: Up to 10 Lambda network-connector ARNs passed to RunMicrovm.<br/>- `cloudwatch_agent.enabled`: Enables the image CloudWatch agent through the shared runner configuration path.<br/>- `cloudwatch_agent.config`: Optional complete CloudWatch agent configuration. Null renders the provider default from `log_files`. Custom log destinations must also be declared in `log_files` so Terraform creates their groups and IAM permissions.<br/>- `log_files`: Optional files collected by the CloudWatch agent. Null uses the MicroVM defaults.<br/>- `log_files[].log_group_name`: CloudWatch log-group name before optional prefixing.<br/>- `log_files[].prefix_log_group`: Prefixes the log-group name with the runner configuration path.<br/>- `log_files[].file_path`: File or glob read by the CloudWatch agent.<br/>- `log_files[].log_stream_name`: Log-stream template. The image replaces `{microvm_id}` with the current MicroVM identifier.<br/>- `log_files[].log_class`: CloudWatch log-group class for the collected file.<br/>- `environment_variables`: Additional provider-specific Lambda environment variables merged into scale-up, scale-down, and pool.<br/>- `iam.resource_arns.images`: Optional MicroVM image ARN allowlist for RunMicrovm and TerminateMicrovm. Null restricts both actions to `image_arn`; set an explicit list when dynamic image overrides are enabled. Provider-required list and connector permissions remain separately scoped to `*`.<br/>- `iam.additional_policy_json.scale_up`: Optional additional provider policy attached separately to the scale-up Lambda role.<br/>- `iam.managed_policies.scale_up`: Optional managed-policy wrapper attached to the scale-up Lambda role. Wrapper presence controls resource creation during planning.<br/>- `iam.managed_policies.scale_up.arn`: ARN of the scale-up managed policy. The ARN may remain unknown until apply.<br/>- `iam.managed_policies.pool`: Optional managed-policy wrapper attached to the pool Lambda role. Wrapper presence controls resource creation during planning.<br/>- `iam.managed_policies.pool.arn`: ARN of the pool managed policy. The ARN may remain unknown until apply. | <pre>object({<br/> image_arn = string<br/> image_version = optional(string, null)<br/> ingress_network_connectors = optional(list(string), [])<br/> egress_network_connectors = optional(list(string), [])<br/> cloudwatch_agent = optional(object({<br/> enabled = optional(bool, true)<br/> config = optional(string, null)<br/> }), {})<br/> log_files = optional(list(object({<br/> log_group_name = string<br/> prefix_log_group = bool<br/> file_path = string<br/> log_stream_name = string<br/> log_class = optional(string, "STANDARD")<br/> })), null)<br/> environment_variables = optional(map(string), {})<br/> iam = optional(object({<br/> resource_arns = optional(object({<br/> images = optional(list(string), null)<br/> }), {})<br/> additional_policy_json = optional(object({<br/> scale_up = optional(string, null)<br/> }), {})<br/> managed_policies = optional(object({<br/> scale_up = optional(object({<br/> arn = string<br/> }), null)<br/> pool = optional(object({<br/> arn = string<br/> }), null)<br/> }), {})<br/> }), {})<br/> })</pre> | n/a | yes |
| <a name="input_observability"></a> [observability](#input\_observability) | Provider-neutral observability settings applied to the provider-managed MicroVM runtime log group.<br/><br/>- `logs.retention_in_days`: CloudWatch Logs retention period.<br/>- `logs.kms_key_id`: Optional KMS key ID or ARN used to encrypt the log group.<br/>- `logs.class`: CloudWatch log-group class.<br/>- `logs.tags`: Tags merged after module-level tags on the log group. | <pre>object({<br/> logs = optional(object({<br/> retention_in_days = optional(number, 180)<br/> kms_key_id = optional(string, null)<br/> class = optional(string, "STANDARD")<br/> tags = optional(map(string), {})<br/> }), {})<br/> })</pre> | `{}` | no |
| <a name="input_prefix"></a> [prefix](#input\_prefix) | Prefix used to identify resources created for the runner configuration. | `string` | `"github-actions"` | no |
| <a name="input_runner"></a> [runner](#input\_runner) | Resolved runner settings consumed by the Lambda MicroVM compute provider.<br/><br/>- `os`: Runner operating system. Lambda MicroVM requires `linux`.<br/>- `architecture`: Runner distribution architecture. Lambda MicroVM requires `arm64`.<br/>- `name_prefix`: Prefix added to registered runner names.<br/>- `run_as_root`: Runs the runner service as root.<br/>- `run_as`: Operating-system user used when `run_as_root` is false.<br/>- `hooks.job_started`: Script installed as the runner job-started hook.<br/>- `hooks.job_completed`: Script installed as the runner job-completed hook.<br/>- `iam.role.arn`: Resolved runner-role ARN used as the MicroVM execution role and referenced by provider policies.<br/>- `iam.role.name`: Resolved runner-role name used by provider resources.<br/>- `iam.role.managed`: Whether runner-config manages the resolved runner role. Callers own an external role and must grant it `ssm:GetParameter` on the lane's `microvm-metadata/*.tags` and `enable_cloudwatch` parameters, `ssm:GetParameter` and `ssm:DeleteParameter` on the lane token path, plus `logs:CreateLogStream` and `logs:PutLogEvents` on the provider-managed runtime log group. When the CloudWatch agent is enabled, it also needs `ssm:GetParameter` on `cloudwatch_agent_config_runner` and stream access to the configured runner log groups.<br/>- `iam.managed_policy_arns`: Common managed-policy ARNs returned with the provider-specific runner policies for attachment by runner-config.<br/>- `iam.path`: IAM path available to provider-managed IAM resources. Null derives the path from `prefix`. | <pre>object({<br/> os = optional(string, "linux")<br/> architecture = optional(string, "arm64")<br/> name_prefix = optional(string, "")<br/> run_as_root = optional(bool, false)<br/> run_as = optional(string, "ec2-user")<br/> hooks = optional(object({<br/> job_started = optional(string, "")<br/> job_completed = optional(string, "")<br/> }), {})<br/> iam = object({<br/> role = object({<br/> arn = string<br/> name = string<br/> managed = optional(bool, true)<br/> })<br/> managed_policy_arns = optional(map(string), {})<br/> path = optional(string, null)<br/> })<br/> })</pre> | n/a | yes |
| <a name="input_storage_provider"></a> [storage\_provider](#input\_storage\_provider) | Storage-provider configuration available to compute-provider bootstrap resources.<br/><br/>- `aws.ssm`: AWS Systems Manager Parameter Store configuration, when SSM is selected.<br/>- `aws.ssm.paths.root`: Root Parameter Store path for the runner configuration.<br/>- `aws.ssm.paths.tokens`: Path segment used for registration tokens and just-in-time configuration.<br/>- `aws.ssm.paths.config`: Path segment used for persistent runner and provider configuration.<br/>- `aws.ssm.tags`: Shared SSM tags that override module-level `tags`.<br/>- `aws.ssm.parameters.tags`: Parameter-specific tags that override module-level and shared SSM tags. | <pre>object({<br/> aws = object({<br/> ssm = object({<br/> paths = object({<br/> root = string<br/> tokens = string<br/> config = string<br/> })<br/> tags = optional(map(string), {})<br/> parameters = optional(object({<br/> tags = optional(map(string), {})<br/> }), {})<br/> })<br/> })<br/> })</pre> | n/a | yes |
| <a name="input_tags"></a> [tags](#input\_tags) | Base tags available to taggable compute-provider resources. Provider-specific tags override this map within their documented scopes. | `map(string)` | `{}` | no |

## Outputs

| Name | Description |
|------|-------------|
| <a name="output_environment_variables"></a> [environment\_variables](#output\_environment\_variables) | Provider-specific Lambda environment variable fragments consumed by runner-config. |
| <a name="output_policies"></a> [policies](#output\_policies) | Provider-specific IAM policy fragments consumed by runner-config. |
| <a name="output_provider"></a> [provider](#output\_provider) | Nested Lambda MicroVM compute-provider contract consumed by runner-config. |
| <a name="output_resources"></a> [resources](#output\_resources) | Provider-specific MicroVM resources exposed by runner-config. |
<!-- END_TF_DOCS -->
116 changes: 116 additions & 0 deletions modules/compute-providers/aws/microvm/control-plane.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
data "aws_iam_policy_document" "scale_up" {
statement {
effect = "Allow"
actions = [
"lambda:ListMicrovms",
"lambda:PassNetworkConnector",
]
resources = ["*"]
}

statement {
effect = "Allow"
actions = [
"lambda:RunMicrovm",
"lambda:TerminateMicrovm",
]
resources = local.microvm_image_resource_arns
}

statement {
effect = "Allow"
actions = [
"ssm:AddTagsToResource",
"ssm:DeleteParameter",
"ssm:PutParameter",
]
resources = [local.microvm_metadata_parameter_arn]
}

statement {
effect = "Allow"
actions = ["ssm:GetParametersByPath"]
resources = [local.microvm_metadata_path_arn, local.microvm_metadata_parameter_arn]
}

statement {
effect = "Allow"
actions = ["ssm:GetParameters"]
resources = [local.microvm_metadata_parameter_arn]
}

statement {
effect = "Allow"
actions = ["iam:PassRole"]
resources = [var.runner.iam.role.arn]
}

statement {
effect = "Allow"
actions = ["ssm:DeleteParameter"]
resources = [local.runner_token_path_arn]
}
}

data "aws_iam_policy_document" "scale_down" {
statement {
effect = "Allow"
actions = ["lambda:ListMicrovms"]
resources = ["*"]
}

statement {
effect = "Allow"
actions = ["lambda:TerminateMicrovm"]
resources = local.microvm_image_resource_arns
}

statement {
effect = "Allow"
actions = [
"ssm:DeleteParameter",
"ssm:PutParameter",
]
resources = [local.microvm_metadata_parameter_arn]
}

statement {
effect = "Allow"
actions = ["ssm:GetParametersByPath"]
resources = [local.microvm_metadata_path_arn, local.microvm_metadata_parameter_arn]
}

statement {
effect = "Allow"
actions = ["ssm:DeleteParameter"]
resources = [local.runner_token_path_arn]
}
}

locals {
microvm_metadata_ssm_path = "${local.ssm_config_ssm_path}/microvm-metadata"
microvm_metadata_path_arn = "${local.ssm_parameter_arn_prefix}${local.microvm_metadata_ssm_path}"
microvm_metadata_parameter_arn = "${local.microvm_metadata_path_arn}/*"
microvm_image_resource_arns = coalesce(
var.config.iam.resource_arns.images,
[var.config.image_arn],
)

microvm_custom_environment_variables = {
for key, value in var.config.environment_variables : key => value
if !contains(["MICROVM_METADATA_TAGS", "MICROVM_RUNNER_CONFIG_SSM_ARN"], key)
}
microvm_environment_variables = merge(local.microvm_custom_environment_variables, {
MICROVM_EGRESS_NETWORK_CONNECTORS = length(var.config.egress_network_connectors) == 0 ? "" : jsonencode(var.config.egress_network_connectors)
MICROVM_EXECUTION_ROLE_ARN = var.runner.iam.role.arn
MICROVM_IMAGE_ARN = var.config.image_arn
MICROVM_IMAGE_VERSION = var.config.image_version == null ? "" : var.config.image_version
MICROVM_INGRESS_NETWORK_CONNECTORS = length(var.config.ingress_network_connectors) == 0 ? "" : jsonencode(var.config.ingress_network_connectors)
MICROVM_LOG_GROUP = aws_cloudwatch_log_group.runtime.name
MICROVM_METADATA_SSM_PATH = local.microvm_metadata_ssm_path
})

scale_up_environment_variables = local.microvm_environment_variables
scale_down_environment_variables = local.microvm_environment_variables

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should include the token path in the scale-down environment before landing this layer. On this head, terraform test in modules/runner-config fails in routes_lambda_microvm_provider because SSM_TOKEN_PATH is absent from the scale-down Lambda. The runtime provider in #5255 also requires it in loadMicrovmProviderConfig, so this would prevent scale-down from loading its configuration. I see the shared scale-down wiring added later in #5280; maybe we should move that small piece here so this Terraform layer and its test can stand on their own.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Once #5473 is merged. I will do it. So it follow the same pattern

pool_environment_variables = local.microvm_environment_variables
}
Loading
Loading