Skip to content

Commit 7036f1b

Browse files
feat(microvm): add Terraform compute provider lane
1 parent 93bd091 commit 7036f1b

20 files changed

Lines changed: 1019 additions & 5 deletions

.github/workflows/terraform.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ jobs:
8787
"multi-runner",
8888
"compute-providers/ec2",
8989
"compute-providers/ec2/trust-policy",
90+
"compute-providers/microvm",
91+
"compute-providers/microvm/trust-policy",
9092
"runner-binaries-syncer",
9193
"runner-stack",
9294
"runner-stack/job-retry",
@@ -228,6 +230,8 @@ jobs:
228230
- modules/runner-stack/ssm-housekeeper
229231
- modules/compute-providers/ec2
230232
- modules/compute-providers/ec2/trust-policy
233+
- modules/compute-providers/microvm
234+
- modules/compute-providers/microvm/trust-policy
231235
defaults:
232236
run:
233237
working-directory: ${{ matrix.module }}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
data "aws_iam_policy_document" "scale_up" {
2+
statement {
3+
effect = "Allow"
4+
actions = local.scale_up_actions
5+
resources = var.config.iam.resource_arns
6+
}
7+
8+
statement {
9+
effect = "Allow"
10+
actions = ["iam:PassRole"]
11+
resources = [local.execution_role_arn]
12+
}
13+
}
14+
15+
data "aws_iam_policy_document" "scale_down" {
16+
statement {
17+
effect = "Allow"
18+
actions = local.scale_down_actions
19+
resources = var.config.iam.resource_arns
20+
}
21+
}
22+
23+
locals {
24+
default_scale_up_actions = [
25+
"lambdamicrovms:CreateMicrovmAuthToken",
26+
"lambdamicrovms:GetMicrovm",
27+
"lambdamicrovms:ListMicrovms",
28+
"lambdamicrovms:RunMicrovm",
29+
"lambdamicrovms:TagResource",
30+
]
31+
32+
default_scale_down_actions = [
33+
"lambdamicrovms:GetMicrovm",
34+
"lambdamicrovms:ListMicrovms",
35+
"lambdamicrovms:ListTags",
36+
"lambdamicrovms:TagResource",
37+
"lambdamicrovms:TerminateMicrovm",
38+
"lambdamicrovms:UntagResource",
39+
]
40+
41+
scale_up_actions = coalesce(var.config.iam.actions.scale_up, local.default_scale_up_actions)
42+
scale_down_actions = coalesce(var.config.iam.actions.scale_down, local.default_scale_down_actions)
43+
44+
execution_role_arn = coalesce(try(var.config.execution_role.arn, null), var.runner.iam.role.arn)
45+
46+
microvm_tags = merge(
47+
var.tags,
48+
var.config.tags,
49+
{
50+
"ghr:Application" = "github-action-runner"
51+
"ghr:environment" = var.prefix
52+
"ghr:runner_name_prefix" = var.runner.name_prefix
53+
},
54+
)
55+
56+
microvm_idle_policy = var.config.idle_policy == null ? null : {
57+
maxIdleDurationSeconds = var.config.idle_policy.max_idle_duration_seconds
58+
suspendedDurationSeconds = var.config.idle_policy.suspended_duration_seconds
59+
autoResumeEnabled = var.config.idle_policy.auto_resume_enabled
60+
}
61+
62+
microvm_logging = var.config.logging == null ? null : (
63+
var.config.logging.disabled ? {
64+
disabled = {}
65+
} : {
66+
cloudWatch = {
67+
logGroup = try(var.config.logging.cloud_watch.log_group, null)
68+
logStream = try(var.config.logging.cloud_watch.log_stream, null)
69+
}
70+
}
71+
)
72+
73+
microvm_run_config = {
74+
imageIdentifier = var.config.image_identifier
75+
imageVersion = var.config.image_version
76+
executionRoleArn = local.execution_role_arn
77+
egressNetworkConnectors = var.config.egress_network_connectors
78+
idlePolicy = local.microvm_idle_policy
79+
logging = local.microvm_logging
80+
runHookPayload = var.config.run_hook_payload
81+
maximumDurationInSeconds = var.config.maximum_duration_in_seconds
82+
tags = local.microvm_tags
83+
}
84+
85+
create_environment_variables = merge(var.config.environment_variables, {
86+
MICROVM_AWS_PARTITION = var.aws_partition
87+
MICROVM_AWS_REGION = var.aws_region
88+
MICROVM_EGRESS_NETWORK_CONNECTORS = jsonencode(var.config.egress_network_connectors)
89+
MICROVM_EXECUTION_ROLE_ARN = local.execution_role_arn
90+
MICROVM_IMAGE_IDENTIFIER = var.config.image_identifier
91+
MICROVM_IMAGE_VERSION = var.config.image_version == null ? "" : var.config.image_version
92+
MICROVM_RUN_CONFIG = jsonencode(local.microvm_run_config)
93+
MICROVM_TAGS = jsonencode(local.microvm_tags)
94+
})
95+
96+
scale_up_environment_variables = local.create_environment_variables
97+
98+
scale_down_environment_variables = merge(var.config.environment_variables, {
99+
MICROVM_AWS_PARTITION = var.aws_partition
100+
MICROVM_AWS_REGION = var.aws_region
101+
MICROVM_IMAGE_IDENTIFIER = var.config.image_identifier
102+
MICROVM_IMAGE_VERSION = var.config.image_version == null ? "" : var.config.image_version
103+
MICROVM_TAGS = jsonencode(local.microvm_tags)
104+
RUNNER_BOOT_TIME_IN_MINUTES = var.runner.boot_time_in_minutes
105+
})
106+
107+
pool_environment_variables = merge(local.create_environment_variables, {
108+
RUNNER_BOOT_TIME_IN_MINUTES = var.runner.boot_time_in_minutes
109+
})
110+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
output "environment_variables" {
2+
description = "Provider-specific Lambda environment variable fragments consumed by runner-stack."
3+
value = local.provider_environment_variables
4+
}
5+
6+
output "policies" {
7+
description = "Provider-specific IAM policy fragments consumed by runner-stack."
8+
value = local.provider_policies
9+
}
10+
11+
output "resources" {
12+
description = "Provider-specific MicroVM resources exposed by runner-stack."
13+
value = local.provider_resources
14+
}
15+
16+
output "provider" {
17+
description = "Nested Lambda MicroVM compute-provider contract consumed by runner-stack."
18+
value = {
19+
environment_variables = local.provider_environment_variables
20+
policies = local.provider_policies
21+
resources = local.provider_resources
22+
}
23+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
locals {
2+
provider_environment_variables = {
3+
scale_up = local.scale_up_environment_variables
4+
scale_down = local.scale_down_environment_variables
5+
pool = local.pool_environment_variables
6+
}
7+
8+
provider_policies = {
9+
runner = {
10+
inline_policies = {}
11+
managed_policy_arns = var.runner.iam.managed_policy_arns
12+
}
13+
scale_up = {
14+
iam_policy_json = data.aws_iam_policy_document.scale_up.json
15+
additional_iam_policy_json = var.config.iam.additional_policy_json.scale_up
16+
managed_policy_enabled = var.config.iam.managed_policy_arns.scale_up != null
17+
managed_policy_arn = var.config.iam.managed_policy_arns.scale_up
18+
}
19+
scale_down = {
20+
iam_policy_json = data.aws_iam_policy_document.scale_down.json
21+
}
22+
pool = {
23+
iam_policy_json = data.aws_iam_policy_document.scale_up.json
24+
managed_policy_enabled = var.config.iam.managed_policy_arns.pool != null
25+
managed_policy_arn = var.config.iam.managed_policy_arns.pool
26+
}
27+
}
28+
29+
provider_resources = {
30+
image_identifier = var.config.image_identifier
31+
image_version = var.config.image_version
32+
execution_role_arn = local.execution_role_arn
33+
}
34+
}
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
mock_provider "aws" {
2+
mock_data "aws_iam_policy_document" {
3+
defaults = {
4+
json = "{}"
5+
}
6+
}
7+
}
8+
9+
variables {
10+
aws_region = "eu-west-1"
11+
prefix = "microvm-test"
12+
13+
tags = {
14+
Module = "runner"
15+
}
16+
17+
config = {
18+
image_identifier = "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner"
19+
image_version = "3"
20+
egress_network_connectors = [
21+
"egress-connector"
22+
]
23+
idle_policy = {
24+
max_idle_duration_seconds = 300
25+
suspended_duration_seconds = 900
26+
auto_resume_enabled = true
27+
}
28+
logging = {
29+
cloud_watch = {
30+
log_group = "/aws/lambdamicrovms/runner"
31+
log_stream = "runtime"
32+
}
33+
}
34+
run_hook_payload = "{\"runner\":\"test\"}"
35+
maximum_duration_in_seconds = 3600
36+
environment_variables = {
37+
MICROVM_CLUSTER = "runner-cluster"
38+
}
39+
tags = {
40+
Provider = "microvm"
41+
}
42+
}
43+
44+
runner = {
45+
boot_time_in_minutes = 7
46+
name_prefix = "microvm-"
47+
iam = {
48+
role = {
49+
arn = "arn:aws:iam::123456789012:role/microvm-test-runner"
50+
name = "microvm-test-runner"
51+
}
52+
managed_policy_arns = {
53+
readonly = "arn:aws:iam::aws:policy/ReadOnlyAccess"
54+
}
55+
}
56+
}
57+
58+
ssm = {
59+
paths = {
60+
root = "/github-action-runners"
61+
tokens = "tokens"
62+
config = "config"
63+
}
64+
}
65+
}
66+
67+
run "exposes_microvm_control_plane_contract" {
68+
command = plan
69+
70+
assert {
71+
condition = toset(keys(output.provider)) == toset(["environment_variables", "policies", "resources"])
72+
error_message = "The MicroVM provider contract must expose only integration and resource data."
73+
}
74+
75+
assert {
76+
condition = (
77+
output.provider.environment_variables.scale_up["MICROVM_CLUSTER"] == "runner-cluster"
78+
&& output.provider.environment_variables.scale_up["MICROVM_IMAGE_IDENTIFIER"] == "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner"
79+
&& output.provider.environment_variables.scale_up["MICROVM_IMAGE_VERSION"] == "3"
80+
&& output.provider.environment_variables.scale_up["MICROVM_EXECUTION_ROLE_ARN"] == "arn:aws:iam::123456789012:role/microvm-test-runner"
81+
&& output.provider.environment_variables.scale_down["RUNNER_BOOT_TIME_IN_MINUTES"] == 7
82+
&& output.provider.environment_variables.pool["RUNNER_BOOT_TIME_IN_MINUTES"] == 7
83+
)
84+
error_message = "The MicroVM provider must expose scale-up, scale-down, and pool environment fragments."
85+
}
86+
87+
assert {
88+
condition = (
89+
jsondecode(output.provider.environment_variables.scale_up["MICROVM_RUN_CONFIG"]).imageIdentifier == "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner"
90+
&& jsondecode(output.provider.environment_variables.scale_up["MICROVM_RUN_CONFIG"]).executionRoleArn == "arn:aws:iam::123456789012:role/microvm-test-runner"
91+
&& jsondecode(output.provider.environment_variables.scale_up["MICROVM_RUN_CONFIG"]).idlePolicy.maxIdleDurationSeconds == 300
92+
&& jsondecode(output.provider.environment_variables.scale_up["MICROVM_RUN_CONFIG"]).logging.cloudWatch.logGroup == "/aws/lambdamicrovms/runner"
93+
&& jsondecode(output.provider.environment_variables.scale_up["MICROVM_TAGS"]).Provider == "microvm"
94+
&& jsondecode(output.provider.environment_variables.scale_up["MICROVM_TAGS"])["ghr:environment"] == "microvm-test"
95+
)
96+
error_message = "The MicroVM provider must encode the RunMicrovm request and protected runner tags."
97+
}
98+
99+
assert {
100+
condition = (
101+
contains(data.aws_iam_policy_document.scale_up.statement[0].actions, "lambdamicrovms:RunMicrovm")
102+
&& contains(data.aws_iam_policy_document.scale_up.statement[0].actions, "lambdamicrovms:CreateMicrovmAuthToken")
103+
&& data.aws_iam_policy_document.scale_up.statement[1].actions == toset(["iam:PassRole"])
104+
&& data.aws_iam_policy_document.scale_up.statement[1].resources == toset(["arn:aws:iam::123456789012:role/microvm-test-runner"])
105+
&& contains(data.aws_iam_policy_document.scale_down.statement[0].actions, "lambdamicrovms:TerminateMicrovm")
106+
)
107+
error_message = "The MicroVM provider must own MicroVM scale-up, scale-down, and PassRole permissions."
108+
}
109+
110+
assert {
111+
condition = (
112+
toset(keys(output.provider.policies)) == toset(["runner", "scale_up", "scale_down", "pool"])
113+
&& length(output.provider.policies.runner.inline_policies) == 0
114+
&& output.provider.policies.runner.managed_policy_arns["readonly"] == "arn:aws:iam::aws:policy/ReadOnlyAccess"
115+
&& !output.provider.policies.scale_up.managed_policy_enabled
116+
&& !output.provider.policies.pool.managed_policy_enabled
117+
)
118+
error_message = "The MicroVM provider must return policy fragments grouped by common component."
119+
}
120+
121+
assert {
122+
condition = output.provider.resources == {
123+
image_identifier = "arn:aws:lambdamicrovms:eu-west-1:123456789012:image/runner"
124+
image_version = "3"
125+
execution_role_arn = "arn:aws:iam::123456789012:role/microvm-test-runner"
126+
}
127+
error_message = "The MicroVM provider must expose its selected image and execution role as provider resources."
128+
}
129+
}
130+
131+
run "accepts_external_execution_role_and_policy_overrides" {
132+
command = plan
133+
134+
variables {
135+
config = {
136+
image_identifier = "runner-image"
137+
execution_role = {
138+
arn = "arn:aws:iam::123456789012:role/external-microvm-execution"
139+
}
140+
logging = {
141+
disabled = true
142+
}
143+
iam = {
144+
resource_arns = ["arn:aws:lambdamicrovms:eu-west-1:123456789012:microvm/*"]
145+
actions = {
146+
scale_up = ["lambdamicrovms:RunMicrovm"]
147+
scale_down = ["lambdamicrovms:TerminateMicrovm"]
148+
}
149+
additional_policy_json = {
150+
scale_up = "{\"Version\":\"2012-10-17\",\"Statement\":[]}"
151+
}
152+
managed_policy_arns = {
153+
scale_up = "arn:aws:iam::123456789012:policy/microvm-scale-up"
154+
pool = "arn:aws:iam::123456789012:policy/microvm-pool"
155+
}
156+
}
157+
}
158+
}
159+
160+
assert {
161+
condition = (
162+
output.provider.environment_variables.scale_up["MICROVM_EXECUTION_ROLE_ARN"] == "arn:aws:iam::123456789012:role/external-microvm-execution"
163+
&& jsondecode(output.provider.environment_variables.scale_up["MICROVM_RUN_CONFIG"]).logging.disabled == {}
164+
&& data.aws_iam_policy_document.scale_up.statement[0].actions == toset(["lambdamicrovms:RunMicrovm"])
165+
&& data.aws_iam_policy_document.scale_up.statement[0].resources == toset(["arn:aws:lambdamicrovms:eu-west-1:123456789012:microvm/*"])
166+
&& data.aws_iam_policy_document.scale_up.statement[1].resources == toset(["arn:aws:iam::123456789012:role/external-microvm-execution"])
167+
&& data.aws_iam_policy_document.scale_down.statement[0].actions == toset(["lambdamicrovms:TerminateMicrovm"])
168+
)
169+
error_message = "External execution role and action/resource overrides must reach the MicroVM provider contract."
170+
}
171+
172+
assert {
173+
condition = (
174+
output.provider.policies.scale_up.additional_iam_policy_json == "{\"Version\":\"2012-10-17\",\"Statement\":[]}"
175+
&& output.provider.policies.scale_up.managed_policy_enabled
176+
&& output.provider.policies.scale_up.managed_policy_arn == "arn:aws:iam::123456789012:policy/microvm-scale-up"
177+
&& output.provider.policies.pool.managed_policy_enabled
178+
&& output.provider.policies.pool.managed_policy_arn == "arn:aws:iam::123456789012:policy/microvm-pool"
179+
)
180+
error_message = "Optional MicroVM policy attachments must stay controlled by object presence."
181+
}
182+
}
183+
184+
run "rejects_empty_image_identifier" {
185+
command = plan
186+
187+
variables {
188+
config = {
189+
image_identifier = " "
190+
}
191+
}
192+
193+
expect_failures = [terraform_data.validate_config]
194+
}

0 commit comments

Comments
 (0)