Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
80 changes: 80 additions & 0 deletions modules/compute-providers/aws/ec2/README.md

Large diffs are not rendered by default.

216 changes: 216 additions & 0 deletions modules/compute-providers/aws/ec2/control-plane.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
# EC2-specific IAM and environment fragments consumed by the common control
# plane in runner-config.
data "aws_iam_policy_document" "ami_id_ssm_parameter_read" {
count = local.ami_id_ssm_external ? 1 : 0

statement {
effect = "Allow"
actions = ["ssm:GetParameter"]
resources = [local.ami_id_ssm_parameter_arn]
}
}

resource "aws_iam_policy" "ami_id_ssm_parameter_read" {
count = local.ami_id_ssm_external ? 1 : 0
name = "${var.prefix}-ami-id-ssm-parameter-read"
path = local.role_path
description = "Allows for reading ${var.prefix} GitHub runner AMI ID from an SSM parameter"
tags = local.provider_tags
policy = data.aws_iam_policy_document.ami_id_ssm_parameter_read[0].json
}

data "aws_iam_policy_document" "scale_up" {
statement {
effect = "Allow"
actions = [
"ec2:DescribeInstances",
"ec2:DescribeLaunchTemplateVersions",
"ec2:DescribeTags",
"ec2:RunInstances",
"ec2:CreateFleet",
"ec2:CreateTags",
]
resources = ["*"]
}

statement {
effect = "Allow"
actions = ["ec2:TerminateInstances"]
resources = ["*"]

condition {
test = "StringEquals"
variable = "ec2:ResourceTag/ghr:Application"
values = ["github-action-runner"]
}
Comment on lines +41 to +45
}

statement {
effect = "Allow"
actions = ["ec2:TerminateInstances"]
resources = ["*"]

condition {
test = "StringEquals"
variable = "ec2:ResourceTag/ghr:environment"
values = [var.prefix]
}
}

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

statement {
effect = "Allow"
actions = ["ssm:GetParameter", "ssm:GetParameters"]
resources = [local.ami_id_ssm_module_managed ? aws_ssm_parameter.runner_ami_id[0].arn : local.ami_id_ssm_parameter_arn]
}

dynamic "statement" {
for_each = local.ami_kms_key_enabled ? [local.ami_kms_key_arn] : []

content {
effect = "Allow"
actions = ["kms:DescribeKey", "kms:ReEncrypt*", "kms:Decrypt"]
resources = [statement.value]
}
}

dynamic "statement" {
for_each = local.ami_kms_key_enabled ? [local.ami_kms_key_arn] : []

content {
effect = "Allow"
actions = ["kms:CreateGrant"]
resources = [statement.value]

condition {
test = "Bool"
variable = "aws:ViaAWSService"
values = ["true"]
}
}
}
}

data "aws_iam_policy_document" "scale_down" {
statement {
effect = "Allow"
actions = ["ec2:DescribeInstances", "ec2:DescribeTags"]
resources = ["*"]
}

statement {
effect = "Allow"
actions = ["ec2:TerminateInstances", "ec2:CreateTags", "ec2:DeleteTags"]
resources = ["*"]

condition {
test = "StringEquals"
variable = "ec2:ResourceTag/ghr:Application"
values = ["github-action-runner"]
}
}

statement {
effect = "Allow"
actions = ["ec2:TerminateInstances", "ec2:CreateTags", "ec2:DeleteTags"]
resources = ["*"]

condition {
test = "StringEquals"
variable = "ec2:ResourceTag/ghr:environment"
values = [var.prefix]
}
}
}

data "aws_iam_policy_document" "pool" {
statement {
effect = "Allow"
actions = [
"ec2:DescribeInstances",
"ec2:DescribeTags",
"ec2:RunInstances",
"ec2:CreateFleet",
"ec2:CreateTags",
]
resources = ["*"]
}

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

statement {
effect = "Allow"
actions = ["ssm:GetParameters"]
resources = [local.ami_id_ssm_module_managed ? aws_ssm_parameter.runner_ami_id[0].arn : local.ami_id_ssm_parameter_arn]
}

dynamic "statement" {
for_each = local.ami_kms_key_enabled ? [local.ami_kms_key_arn] : []

content {
effect = "Allow"
actions = ["kms:DescribeKey", "kms:ReEncrypt*", "kms:Decrypt"]
resources = [statement.value]
}
}

dynamic "statement" {
for_each = local.ami_kms_key_enabled ? [local.ami_kms_key_arn] : []

content {
effect = "Allow"
actions = ["kms:CreateGrant"]
resources = [statement.value]

condition {
test = "Bool"
variable = "aws:ViaAWSService"
values = ["true"]
}
}
}
}

data "aws_iam_policy_document" "service_linked_role" {
count = var.config.create_service_linked_role_spot ? 1 : 0

statement {
effect = "Allow"
actions = ["iam:CreateServiceLinkedRole"]
resources = ["arn:${var.aws_partition}:iam::*:role/aws-service-role/*"]
}
}

locals {
scale_up_environment_variables = {
AMI_ID_SSM_PARAMETER_NAME = local.ami_id_ssm_parameter_name
INSTANCE_ALLOCATION_STRATEGY = var.config.instance_allocation_strategy
INSTANCE_MAX_SPOT_PRICE = var.config.instance_max_spot_price
INSTANCE_TARGET_CAPACITY_TYPE = var.config.instance_target_capacity_type
INSTANCE_TYPE_PRIORITIES = var.config.instance_type_priorities != null ? jsonencode(var.config.instance_type_priorities) : ""
INSTANCE_TYPES = join(",", var.config.instance_types)
LAUNCH_TEMPLATE_NAME = aws_launch_template.runner.name
SUBNET_IDS = join(",", var.config.subnet_ids)
ENABLE_ON_DEMAND_FAILOVER_FOR_ERRORS = jsonencode(var.config.on_demand_failover_for_errors)
SCALE_ERRORS = jsonencode(var.config.scale_errors)
USE_DEDICATED_HOST = var.config.use_dedicated_host
}

scale_down_environment_variables = {}

pool_environment_variables = local.scale_up_environment_variables

scale_up_iam_policy_json = data.aws_iam_policy_document.scale_up.json
scale_down_iam_policy_json = data.aws_iam_policy_document.scale_down.json
pool_iam_policy_json = data.aws_iam_policy_document.pool.json
service_linked_role_policy_json = var.config.create_service_linked_role_spot ? data.aws_iam_policy_document.service_linked_role[0].json : null
}
9 changes: 9 additions & 0 deletions modules/compute-providers/aws/ec2/instance-profile.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# The common runner configuration owns the role; EC2 owns the profile consumed by its
# launch template.
resource "aws_iam_instance_profile" "runner" {
count = var.config.instance_profile == null ? 1 : 0
name = "${var.prefix}-runner-profile"
role = var.runner.iam.role.name
path = local.instance_profile_path
tags = local.provider_tags
}
75 changes: 75 additions & 0 deletions modules/compute-providers/aws/ec2/logging.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# EC2 runner log collection and CloudWatch resources.
locals {
runner_log_files = (
var.config.log_files != null
? var.config.log_files
: [
{
"prefix_log_group" : true,
"file_path" : "/var/log/messages",
"log_group_name" : "messages",
"log_stream_name" : "{instance_id}",
"log_class" : "STANDARD"
},
{
"log_group_name" : "user_data",
"prefix_log_group" : true,
"file_path" : var.runner.os == "windows" ? "C:/UserData.log" : "/var/log/user-data.log",
"log_stream_name" : "{instance_id}",
"log_class" : "STANDARD"
},
{
"log_group_name" : "runner",
"prefix_log_group" : true,
"file_path" : var.runner.os == "windows" ? "C:/actions-runner/_diag/Runner_*.log" : "/opt/actions-runner/_diag/Runner_**.log",
"log_stream_name" : "{instance_id}",
"log_class" : "STANDARD"
},
{
"log_group_name" : "runner-startup",
"prefix_log_group" : true,
"file_path" : var.runner.os == "windows" ? "C:/runner-startup.log" : "/var/log/runner-startup.log",
"log_stream_name" : "{instance_id}",
"log_class" : "STANDARD"
}
]
)
# CloudWatch agent collect_list schema expects log_group_class, not log_class
logfiles = var.config.cloudwatch_agent.enabled ? [for l in local.runner_log_files : {
"log_group_name" : l.prefix_log_group ? "/github-self-hosted-runners/${var.prefix}/${l.log_group_name}" : "/${l.log_group_name}"
"log_stream_name" : l.log_stream_name
"file_path" : l.file_path
"log_group_class" : l.log_class
}] : []

loggroups_names = distinct([for l in local.logfiles : l.log_group_name])
# Create a list of unique log classes corresponding to each log group name
# This maintains the same order as loggroups_names for use with count
loggroups_classes = [
for name in local.loggroups_names : [
for l in local.logfiles : l.log_group_class
if l.log_group_name == name
][0]
]

}


resource "aws_ssm_parameter" "cloudwatch_agent_config_runner" {
count = var.config.cloudwatch_agent.enabled ? 1 : 0
name = "${var.ssm.paths.root}/${var.ssm.paths.config}/cloudwatch_agent_config_runner"
type = "String"
value = var.config.cloudwatch_agent.config != null ? var.config.cloudwatch_agent.config : templatefile("${path.module}/templates/cloudwatch_config.json", {
logfiles = jsonencode(local.logfiles)
})
tags = local.ssm_parameter_tags
}

resource "aws_cloudwatch_log_group" "gh_runners" {
count = length(local.loggroups_names)
name = local.loggroups_names[count.index]
retention_in_days = var.observability.logs.retention_in_days
kms_key_id = var.observability.logs.kms_key_id
log_group_class = local.loggroups_classes[count.index]
tags = local.log_group_tags
}
23 changes: 23 additions & 0 deletions modules/compute-providers/aws/ec2/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
output "environment_variables" {
description = "Provider-specific Lambda environment variable fragments consumed by runner-config."
value = local.provider_environment_variables
}

output "policies" {
description = "Provider-specific IAM policy fragments consumed by runner-config."
value = local.provider_policies
}

output "resources" {
description = "Provider-specific EC2 resources exposed by runner-config."
value = local.provider_resources
}

output "provider" {
description = "Nested EC2 compute-provider contract consumed by runner-config."
value = {
environment_variables = local.provider_environment_variables
policies = local.provider_policies
resources = local.provider_resources
}
}
Loading
Loading